NOTEBOOK - Stéphane Srsa

PROJET 6

Analysez les ventes d'une librairie

No description has been provided for this image

SOMMAIRE

1 - Importation des librairies et des fichiers

1.1 - Importation des librairies

1.2 - Importation des librairies statistiques

1.3 - Importation des fichiers

1.4 - Création des fonctions

2 - Analyse exploratoire des fichiers

2.1 - Analyse exploratoire du fichier 'customers'

2.2 - Analyse exploratoire du fichier 'products'

2.3 - Analyse exploratoire du fichier 'transactions'

3 - Traitement des fichiers en vu des analyses

3.1 - Jointure des tables 'transactions' et 'products'

3.2 - Jointure des tables 'transactions' et 'customers'

3.3 - Nettoyage/Préparation du Df 'transanalyse' avec lequel les analyses seront réalisées

4 - Analyses sur le Chiffre d'affaires

4.1 - Chiffres d'affaires total et mensuel sur les deux dernières années

4.2 - Calcul du panier moyen

4.3 - Analyse sur le turnover clients

4.4 - Chiffre d'affaires par catégorie

4.5 - Chiffre d'affaires réalisé en fonction de l'âge / tranches d'âges

4.6 - Moyennes mobiles (Tendance globale)

4.7 - Projection du Chiffre d'affaires avec Prophet

5 - Analyses sur les variables

5.1 - Variable 'Prix'

5.2 - Variable 'Age'

5.3 - Comparaison de distribution sur les groupes 'Femmes'/'Hommes'

5.4 - Mesures de tendance centrale sur les catégories

6 - Etude sur les références / catégories

6.1 - Distribution empirique des catégories

6.2 - Courbes de Pareto sur les catégories

6.3 - Quantités de livres vendus : Top et Flop des catégories

6.4 - Top Chiffres d'Affaires toutes catégories

7 - Profil clientèle - Répartition du CA

7.1 - Distribution empirique des ages

7.2 - Analyse du panier moyen par tranche d'âges et nombre de sessions

7.3 - Analayse sur les sessions

7.4 - Comparaison entre nombre de clients et Ca réalisé par tranche d'âges

7.5 - Répartition du Chiffre d'affaires

7.6 - Analyse de la saisonnalité

8 - Etude sur les corrélations

8.1 - Corrélation entre le genre client et la catégorie de livre acheté

8.2 - Corrélation entre l'âge des clients et le montant total des achats

8.3 - Corrélation entre l'âge des clients et la fréquence d'achat

8.4 - Corrélation entre l'âge des clients et la taille du panier moyen

8.5 - Corrélation entre l'âge des clients et la catégorie des livre acheté

1 - Importation des librairies et chargement des fichiers

1.1 - Importation des librairies

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
import locale
import pytz
import seaborn as sns
from IPython.display import display, HTML
import locale
from matplotlib.patches import Ellipse
import warnings
warnings.filterwarnings("ignore")

1.2 - Importation des librairies statistiques

In [2]:
import scipy.stats as st
from scipy.stats import chi2_contingency as chi2
import statsmodels.api as sm
from seaborn_qqplot import pplot
from statsmodels.formula.api import ols
from scipy.stats import shapiro, pearsonr, anderson, spearmanr, kruskal, kstest, norm, mannwhitneyu, linregress
from sklearn.preprocessing import StandardScaler
from bioinfokit.analys import stat
import pylab 
from prophet import Prophet
from prophet.plot import plot_plotly, plot_components_plotly

1.3 - Importation des fichiers

In [3]:
customers= pd.read_csv('customers.csv')
products = pd.read_csv('products.csv')
transactions = pd.read_csv('transactions.csv')

1.4 - Création des fonctions

In [4]:
# Fonction pour afficher des messages plus esthétiques
def Text_message(message):
    max_line_length = 100  # Longueur maximale d'une ligne avant de sauter à la ligne suivante
    lines = []
    current_line = ""

    for mot in message.split():
        if len(current_line + mot) <= max_line_length:
            current_line += mot + " "
        else:
            lines.append(current_line.strip())
            current_line = mot + " "
    
    # Ajoute la dernière ligne restante
    lines.append(current_line.strip())
    
    formatted_lines = "<br>".join(lines)
    styled_message = '<div style="text-align: left;"><span style="font-weight: bold; font-style: italic; font-family: Times New Roman;color: teal; font-size: 12pt; font-family: Arial;">{}</span></div>'.format(formatted_lines)
    display(HTML(styled_message))
In [68]:
# Fonction pour afficher des messages plus esthétiques
def Titre_message(message):
    max_line_length = 90  # Longueur maximale d'une ligne avant de sauter à la ligne suivante
    lines = []
    current_line = ""

    for mot in message.split():
        if len(current_line + mot) <= max_line_length:
            current_line += mot + " "
        else:
            lines.append(current_line.strip())
            current_line = mot + " "
    
    # Ajoute la dernière ligne restante
    lines.append(current_line.strip())
    
    formatted_lines = "<br>".join(lines)
    styled_message = '<div style="text-align: center;"><span style="font-weight: bold; font-style: italic; font-family: Times New Roman;color: firebrick; font-size: 14pt; font-family: Arial;">{}</span></div>'.format(formatted_lines)
    display(HTML(styled_message))
In [6]:
# Pour Courbes de Pareto : Fonction pour définir l'égalité %Qtés vendues et %Nbr produits
def seuil_ventes(df):
    tot = df['Ventes'].count()
    for i in range(70, 90):
        seuil = df[df['Pourcentage_cumulé'] <= i]['Ventes'].count()
        Comp = (tot * (100 - i)) / 100
        if Comp - 1 <= seuil <= Comp + 1:
            pour= i
            break
    return Comp, pour, seuil, tot

Etape 2 - Analyse exploratoire des fichiers

2.1 - Analyse exploratoire du fichier 'customers'

In [7]:
# Lecture des informations du fichier
display(customers.tail(3))
message = "le fichier contient {:.0f} lignes et {} colonnes".format(customers.shape[0],customers.shape[1])
Text_message(message)
print('')
customers.info()
client_id sex birth
8620 c_5119 m 1974
8621 c_5643 f 1968
8622 c_84 f 1982
le fichier contient 8623 lignes et 3 colonnes
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8623 entries, 0 to 8622
Data columns (total 3 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   client_id  8623 non-null   object
 1   sex        8623 non-null   object
 2   birth      8623 non-null   int64 
dtypes: int64(1), object(2)
memory usage: 202.2+ KB
In [8]:
# Vérification valeurs manquantes
print(customers.isnull().sum())

# Détection des doublons
print('      --       ')
print(customers.loc[customers['client_id'].duplicated(keep=False),:])
customers.loc[customers[['client_id', 'sex', 
             'birth']].duplicated(keep=False),:].sort_values(by=['client_id',
                                               'sex'], ascending=True)
# Vérification de l'unicité de la clé 'client_id'
if customers.size == customers.drop_duplicates('client_id').size :
    message = "'client_id' peut être ultilé comme clé primaire"
else:
    message = "'client_id' ne peut pas être ultilé comme clé primaire"
Text_message(message)
client_id    0
sex          0
birth        0
dtype: int64
      --       
Empty DataFrame
Columns: [client_id, sex, birth]
Index: []
'client_id' peut être ultilé comme clé primaire

Le fichier 'customers' ne contient aucune valeur manquante, ni de doublons.

Nous pouvons utiliser 'client_id' comme clé primaire.

2.2 - Analyse exploratoire du fichier 'products'

In [9]:
# Lecture des information du fichier
display(products.tail(3))
message = "le fichier contient {:.0f} lignes et {} colonnes".format(products.shape[0],products.shape[1])
Text_message(message)
print('')
products.info()
id_prod price categ
3284 0_802 11.22 0
3285 1_140 38.56 1
3286 0_1920 25.16 0
le fichier contient 3287 lignes et 3 colonnes
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3287 entries, 0 to 3286
Data columns (total 3 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   id_prod  3287 non-null   object 
 1   price    3287 non-null   float64
 2   categ    3287 non-null   int64  
dtypes: float64(1), int64(1), object(1)
memory usage: 77.2+ KB
In [10]:
# Vérification valeurs manquantes
print(products.isnull().sum())
print('      --       ')
# Détection des doublons
print(products.loc[products['id_prod'].duplicated(keep=False),:])
products.loc[products[['id_prod', 'price', 
             'categ']].duplicated(keep=False),:].sort_values(by=['id_prod',
                                               'price'], ascending=True)
# Vérification de l'unicité de la clé 'id_prod'
if products.size == products.drop_duplicates('id_prod').size :
    message = "'id_prod' peut être ultilé comme clé primaire"
else:
    message = "'id_prod' ne peut pas être ultilé comme clé primaire"
Text_message(message)
id_prod    0
price      0
categ      0
dtype: int64
      --       
Empty DataFrame
Columns: [id_prod, price, categ]
Index: []
'id_prod' peut être ultilé comme clé primaire

Le fichier 'products' ne contient aucune valeur manquante, ni de doublons.

Nous pouvons utiliser 'id_prod' comme clé primaire.

2.3 - Analyse exploratoire du fichier 'transactions'

In [11]:
# Lecture des information du fichier
display(transactions.tail(3))
message = "le fichier contient {:.0f} lignes et {} colonnes".format(transactions.shape[0],transactions.shape[1])
Text_message(message)
print('')
transactions.info()
id_prod date session_id client_id
679529 0_1425 2022-12-20 04:33:37.584749 s_314704 c_304
679530 0_1994 2021-07-16 20:36:35.350579 s_63204 c_2227
679531 1_523 2022-09-28 01:12:01.973763 s_274568 c_3873
le fichier contient 679532 lignes et 4 colonnes
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 679532 entries, 0 to 679531
Data columns (total 4 columns):
 #   Column      Non-Null Count   Dtype 
---  ------      --------------   ----- 
 0   id_prod     679532 non-null  object
 1   date        679532 non-null  object
 2   session_id  679532 non-null  object
 3   client_id   679532 non-null  object
dtypes: object(4)
memory usage: 20.7+ MB
In [12]:
# Vérification valeurs manquantes
message = "le fichier n'a pas de valeur manquante"
Text_message(message)
print(transactions.isnull().sum())
# Détection des doublons
transactions.loc[transactions[['id_prod', 'session_id', 
             'client_id']].duplicated(keep=False),:].sort_values(by=['session_id',
                                               'id_prod'], ascending=True)

# Enregistrement des tests dans une table spécifique
transactions_test = transactions.loc[transactions['id_prod'] == 'T_0', :]
transactions_test = transactions_test.reset_index()
# Suppression des tests dans la table 'transactions'
transactions.drop(transactions[transactions['id_prod'] == 'T_0'].index, inplace=True)

# Affichage de la table 'transactions_test'
message = "le fichier 'transactions_test' contient {:.0f} lignes et {} colonnes".format(transactions_test.shape[0],transactions_test.shape[1])
Text_message(message)
display(transactions_test.tail(3))
le fichier n'a pas de valeur manquante
id_prod       0
date          0
session_id    0
client_id     0
dtype: int64
le fichier 'transactions_test' contient 200 lignes et 5 colonnes
index id_prod date session_id client_id
197 670680 T_0 test_2021-03-01 02:30:02.237449 s_0 ct_1
198 671647 T_0 test_2021-03-01 02:30:02.237424 s_0 ct_1
199 679180 T_0 test_2021-03-01 02:30:02.237425 s_0 ct_1
In [13]:
# Enregistrement des doublons dans une table spécifique
transactions_double = transactions.loc[transactions[['id_prod', 'session_id', 
             'client_id']].duplicated(keep=False),:].sort_values(by=['date',
                                               'id_prod'], ascending=True)
# Suppression des doublons dans la table 'transactions'
transactions = transactions.drop_duplicates(subset=['id_prod', 'session_id', 'client_id'], 
                                    keep='first').sort_values(by=['session_id'], 
                                                              ascending=True).reset_index()
# Suppression de la colonne 'index'
transactions = transactions.drop(['index'], axis=1)
message = "Le fichier 'transactions' contient, après traitement, {:.0f} lignes et {} colonnes".format(transactions.shape[0],transactions.shape[1])
Text_message(message)
transactions.tail(3)
Le fichier 'transactions' contient, après traitement, 678505 lignes et 4 colonnes
Out[13]:
id_prod date session_id client_id
678502 0_1604 2021-10-04 19:00:20.030388 s_99998 c_2795
678503 0_1572 2021-10-04 18:45:54.374885 s_99998 c_2795
678504 0_1411 2021-10-04 18:50:59.099942 s_99998 c_2795

Le fichier 'transactions' ne contient aucune valeur manquante, les doublons ont été supprimés.

Les 200 tests on été supprimés.

3 - Traitement des fichiers en vu des analyses

3.1 - Jointure des tables 'transactions' et 'products'

In [14]:
# Jointure des tables liaison et products
transa = pd.merge(transactions, customers, on=['client_id'], how ='left', indicator=True)
display(transa.tail())

# Vérification de la jointure
message = "Il y a {:.0f} jointure différente de 'both'".format(transa[transa['_merge']!='both'].shape[0])
Text_message(message)
# Vérification valeurs manquantes
print('---------')
message = "Vérification de valeur nulle"
Text_message(message)
print(transa.isnull().sum())
id_prod date session_id client_id sex birth _merge
678500 0_2231 2021-10-04 18:38:36.333661 s_99996 c_4900 f 1974 both
678501 0_1197 2021-10-04 18:45:38.003516 s_99997 c_3521 f 1979 both
678502 0_1604 2021-10-04 19:00:20.030388 s_99998 c_2795 f 1978 both
678503 0_1572 2021-10-04 18:45:54.374885 s_99998 c_2795 f 1978 both
678504 0_1411 2021-10-04 18:50:59.099942 s_99998 c_2795 f 1978 both
Il y a 0 jointure différente de 'both'
---------
Vérification de valeur nulle
id_prod       0
date          0
session_id    0
client_id     0
sex           0
birth         0
_merge        0
dtype: int64
In [15]:
# Suppression de la colonne '_merge'
transa = transa.drop(['_merge'], axis=1)

3.2 - Jointure des tables 'transactions' et 'customers'

In [16]:
# Jointure des tables transa et products
transanalyse = pd.merge(transa, products, on=['id_prod'], how ='left', indicator=True)
display(transanalyse.tail(3))

# Vérification de la jointure
message = "Il y a {:.0f} jointures différentes de 'both'".format(transanalyse[transanalyse['_merge']!='both'].shape[0])
Text_message(message)

# Vérification valeurs manquantes
print('---------')
print('Vérification de valeur nulle')
print(transanalyse.isnull().sum())
id_prod date session_id client_id sex birth price categ _merge
678502 0_1604 2021-10-04 19:00:20.030388 s_99998 c_2795 f 1978 17.51 0.0 both
678503 0_1572 2021-10-04 18:45:54.374885 s_99998 c_2795 f 1978 8.61 0.0 both
678504 0_1411 2021-10-04 18:50:59.099942 s_99998 c_2795 f 1978 8.99 0.0 both
Il y a 221 jointures différentes de 'both'
---------
Vérification de valeur nulle
id_prod         0
date            0
session_id      0
client_id       0
sex             0
birth           0
price         221
categ         221
_merge          0
dtype: int64
In [17]:
# Vérification des 221 jointures différentes de 'both'
display(transanalyse[transanalyse['_merge']!='both'].tail(3))

# Création d'un Df reprenant les id_prod = 0_2245
Prod2245 = transanalyse[transanalyse['_merge']!='both']

# Vérification de l'existence du id_prod = 0_2245 dans le Df 'products'
message ="Vérification dans le fichier 'products'" 
Text_message(message)
display(products[products['id_prod'] =='0_2245'])
message ="Il n'y a pas de référence 0_2245 dans le fichier 'products'"
Text_message(message)
# Suppression de la colonne '_merge' 
transanalyse = transanalyse.drop('_merge', axis=1)
id_prod date session_id client_id sex birth price categ _merge
667943 0_2245 2021-09-25 17:07:45.271509 s_95396 c_8616 m 1984 NaN NaN left_only
669293 0_2245 2021-09-26 17:52:55.561657 s_95930 c_6990 f 1986 NaN NaN left_only
678327 0_2245 2021-10-04 13:36:19.612226 s_99885 c_1514 f 1982 NaN NaN left_only
Vérification dans le fichier 'products'
id_prod price categ
Il n'y a pas de référence 0_2245 dans le fichier 'products'
In [18]:
# Réinitialisation de l'index et suppression de la colonne 'index'
transanalyse = transanalyse.reset_index()
transanalyse = transanalyse.drop('index', axis=1)

# Conversion en datetime de la colonne 'date'
transanalyse['date'] = pd.to_datetime(transanalyse['date'])
# Ajout de colonnes aux formats utiles pour les analyses
message ="Ajout des colonnes 'year', 'month' et 'day'"
Titre_message(message)
transanalyse['year'] = transanalyse['date'].dt.year
transanalyse['month'] = transanalyse['date'].dt.strftime('%Y-%m')
transanalyse['day'] = transanalyse['date'].dt.strftime('%Y-%m-%d')
# Conversion en datetime de la colonne 'day'
transanalyse['day'] = pd.to_datetime(transanalyse['day'])
transanalyse.head(3)
Ajout des colonnes 'year', 'month' et 'day'
Out[18]:
id_prod date session_id client_id sex birth price categ year month day
0 0_1259 2021-03-01 00:01:07.843138 s_1 c_329 f 1967 11.99 0.0 2021 2021-03 2021-03-01
1 1_635 2021-03-01 00:10:33.163037 s_10 c_2218 f 1970 26.99 1.0 2021 2021-03 2021-03-01
2 0_1030 2021-03-01 04:12:43.572994 s_100 c_3854 f 1978 13.73 0.0 2021 2021-03 2021-03-01

3.3 - Nettoyage/Préparation du Df 'transanalyse' avec lequel les analyses seront réalisées

Vérification des incohérences.

In [19]:
# Infos avant traitement
display(transanalyse.categ.unique())
display(transanalyse.value_counts('categ'))
array([ 0.,  1.,  2., nan])
categ
0.0    415010
1.0    226891
2.0     36383
Name: count, dtype: int64
In [20]:
# Conversion du type 'categ' en object
transanalyse["categ"] = transanalyse["categ"].astype(str)

# Calcul du Chiffre d'affaires jour par catégorie
Ca_categ_jour = transanalyse.groupby(['day','categ'])[['price']].sum().reset_index()
Ca_categ_jour = Ca_categ_jour.sort_values(by=['day'], ascending=True)
# Changement du nom des colonnes 'price' par 'CA/jour'
Ca_categ_jour = Ca_categ_jour.rename(columns={'day':'jour','price':"CA/jour"})
# Mise au format de la colonne  'CA/jour'
Ca_categ_jour["CA/jour"] = round((Ca_categ_jour["CA/jour"]),2)
# Affichage si souhaité
# display(Ca_categ_jour.head(3))
# Création d'un DF pour la catégorie 1.0
Ca_cat0 = Ca_categ_jour.loc[Ca_categ_jour['categ'] == '1.0']

# Vérification du nombre d'enregistrements par catégorie
print('_________________________________________________')
message = "Vérification du nombre d'enregistrements par catégorie"
Text_message(message)
display(Ca_categ_jour.value_counts('categ'))
message = "Sur la période de 2 ans observée, il manque 26 enregistrements dans la catégorie 1.0 sur le mois d'octobre 2021"
Text_message(message)
message = "Nous avons également 194 enregistrements non associés à une catégorie"
Text_message(message)
print('_________________________________________________')

# Modification du DF pour avoir les catégories en colonne
Ca_categ_Grap = Ca_categ_jour.pivot_table(index='jour', columns='categ', 
                                          values="CA/jour",
                                          margins=False).reset_index()
# affichage si souahaité 
# display(Ca_categ_Grap.head(2))

# Modification du DF en vu de la création du graphique
Ca_categ_Grap = Ca_categ_Grap.melt('jour', var_name="var", value_name="CA")

# Selection de l'interval 'temps' souhaité
Ca_categ_Grap=Ca_categ_Grap[(Ca_categ_Grap['jour'] > '2021-09-01') & (Ca_categ_Grap['jour'] < '2021-11-30')]

# Vérification des dates manquantes de la catégorie 1.0
message = "Mise en avant des valeurs manquantes de la catégorie 1.0"
Text_message(message)
Ca_cat0_verif=Ca_cat0[(Ca_cat0['jour'] > '2021-09-30') & (Ca_cat0['jour'] < '2021-10-30')]
display(Ca_cat0_verif.head())

# Génération du Graphique   
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Chiffre d'Affaires par catégorie - Selon fichiers fournis\nPerte de données sur la catégorie 1.0", y=1.05, fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

gfg=sns.lineplot(x='jour', y="CA", hue='var', data=Ca_categ_Grap, palette=['lightgrey', 'firebrick','teal','red'],linewidth=3, legend=True)
# Définir les coordonnées et les dimensions de l'ovale
x_center = pd.to_datetime('2021-10-15')  # Coordonnée x du centre de l'ovale
y_center = 6800   # Coordonnée y du centre de l'ovale
width = 28     # Largeur de l'ovale
height = 3000     # Hauteur de l'ovale
angle = 0.1     # Angle de rotation 

# Créer l'ovale
ellipse = Ellipse((x_center, y_center), width=width, height=height, angle=angle, edgecolor='black', facecolor='w', alpha=0.4)

# Ajouter l'ovale au graphique
gfg.add_patch(ellipse)
legend = gfg.legend(loc='lower right', shadow=True, fontsize='medium', facecolor='whitesmoke', framealpha=0.7)
legend.get_frame().set_facecolor('whitesmoke')
plt.xticks(rotation=30, fontsize=10)
plt.show()
_________________________________________________
Vérification du nombre d'enregistrements par catégorie
categ
0.0    730
2.0    730
1.0    704
nan    194
Name: count, dtype: int64
Sur la période de 2 ans observée, il manque 26 enregistrements dans la catégorie 1.0 sur le mois
d'octobre 2021
Nous avons également 194 enregistrements non associés à une catégorie
_________________________________________________
Mise en avant des valeurs manquantes de la catégorie 1.0
jour categ CA/jour
698 2021-10-01 1.0 6989.80
758 2021-10-28 1.0 6317.99
761 2021-10-29 1.0 6425.18
No description has been provided for this image

Choix du traitement à apporter.

- Pour les 26 enregistrements manquants : Ajouter des lignes par date avec un CA basé sur le CA moyen de la catégorie 1.0 ou Création aléatoire des Ca/jour (Hypothèse : CA récupérer auprès de la comptabilité.

- Pour le produit 0_2245, il appartient à la catégorie 0.0, le prix va être calculer sur le prix moyen de la catégorie 0.0

Imputation du prix pour le produit 0_2245

In [21]:
################## Vérification du prix moyen par catégorie #################

# Calcul du Chiffre d'affaires par catégorie
Ca_par_categ = transanalyse.groupby('categ')['price'].sum().reset_index()
Ca_par_categ = Ca_par_categ.sort_values(by=['categ'], ascending=True)
# Changement du nom des colonnes 'price' par 'Chiffre d'affaires en million' et 'categ' par 'Catégorie'
Ca_par_categ = Ca_par_categ.rename(columns={'categ': "Catégorie", 
                                            'price':"CA en million"})
# Ramener le CA en Million
Ca_par_categ["CA en million"] = round((Ca_par_categ["CA en million"]/1000000),3)
# Calcul des quantités par catégorie
Catnb = transanalyse.value_counts('categ')
Catnb = Catnb.reset_index()
Catnb = Catnb.rename(columns={"categ":"Catégorie","count":"Quantité"})
Catnb = Catnb.rename(columns={0:"Quantité"})
Catnb["Catégorie"] = Catnb["Catégorie"].astype(str)
message = "Vérification du prix moyen de la catégorie 0.0"
Text_message(message)
#  Jointure des deux tables et calcule du prix moyen par catégorie
cat = pd.merge(Ca_par_categ, Catnb, on=['Catégorie'], how ='left', indicator=True)
#  Jointure des deux tables et calcule du prix moyen par catégorie
cat = pd.merge(Ca_par_categ, Catnb, on=['Catégorie'], how ='left', indicator=True)
cat['prix moyen'] = round(((cat["CA en million"] * 1000000) / cat['Quantité']),2)
# Suppression de la colonne '_merge'
cat = cat.drop(['_merge'], axis=1)
display(cat)
Vérification du prix moyen de la catégorie 0.0
Catégorie CA en million Quantité prix moyen
0 0.0 4.415 415010 10.64
1 1.0 4.648 226891 20.49
2 2.0 2.774 36383 76.24
3 nan 0.000 221 0.00
In [22]:
message = "Vérification du prix moyen de la catégorie 0.0"
Text_message(message)

#  Jointure des deux tables et calcule du prix moyen par catégorie
cat = pd.merge(Ca_par_categ, Catnb, on=['Catégorie'], how ='left', indicator=True)
cat['prix moyen'] = round(((cat["CA en million"] * 1000000) / cat['Quantité']),2)
# Suppression de la colonne '_merge'
cat = cat.drop(['_merge'], axis=1)
display(cat)

# graphique distribution des catégories
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.subplot(1,2,1)
plt.title("Niveau de prix par catégorie", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.boxplot(data=transanalyse, x="price", y="categ", showfliers=False, showmeans=True,notch = True, palette='crest',
            medianprops=dict(linestyle='-', linewidth=2, color='coral'),
            meanprops=dict(marker='p',markerfacecolor='red', markeredgecolor='black', markersize=8),
            boxprops = dict(linestyle='-', linewidth=0))
plt.xticks(rotation=0, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Catégories")
plt.xlabel('Prix')
plt.subplot(1,2,2)
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.title("Niveau de prix par catégorie", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
ax = sns.boxenplot(data=transanalyse, x="price", y="categ", showfliers=False, 
            palette='crest')
plt.xticks(rotation=0, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Catégories")
plt.xlabel('Prix')
plt.show() 

################## Modification du produit '0_2245' #################

message = "Enregistrement des données pour le produit 0_2245 dans la table"
Text_message(message)
# Mettre le prix à 10.64 et la catégorie à 0.0 pour le produit '0_2245'
transanalyse.fillna(0, inplace=True)
transanalyse.loc[transanalyse["id_prod"] == "0_2245", "price"] = 10.64
transanalyse.loc[transanalyse["id_prod"] == "0_2245", "categ"] = 0.0
# Convertir 'categ' en type object
transanalyse["categ"] = transanalyse["categ"].astype(str)
# Affichage 
display(transanalyse.loc[transanalyse['id_prod'] == '0_2245'].head(3))
display(transanalyse.categ.unique())
transanalyse.value_counts('categ')

##################### Màj des DF 'Ca_categ_jour' et 'Ca_par_categ'#####################

# Mise à jour du DF en intégrant le produit 0_2245
Ca_categ_jour = transanalyse.groupby(['day','categ'])[['price']].sum().reset_index()
Ca_categ_jour = Ca_categ_jour.sort_values(by=['day'], ascending=True)
# Changement du nom des colonnes 'price' par 'CA/jour'
Ca_categ_jour = Ca_categ_jour.rename(columns={'day':'jour','price':"CA/jour"})
# Mise au format de la colonne  'CA/jour'
Ca_categ_jour["CA/jour"] = round((Ca_categ_jour["CA/jour"]),2)
# Mise à jour du DF 'Ca_par_categ'
Ca_par_categ = transanalyse.groupby('categ')['price'].sum().reset_index()
Ca_par_categ = Ca_par_categ.sort_values(by=['categ'], ascending=True)
Ca_par_categ = Ca_par_categ.rename(columns={'categ': "Catégorie", 'price':"CA en million"})
Ca_par_categ["CA en million"] = round((Ca_par_categ["CA en million"]/1000000),3)
Vérification du prix moyen de la catégorie 0.0
Catégorie CA en million Quantité prix moyen
0 0.0 4.415 415010 10.64
1 1.0 4.648 226891 20.49
2 2.0 2.774 36383 76.24
3 nan 0.000 221 0.00
No description has been provided for this image
Enregistrement des données pour le produit 0_2245 dans la table
id_prod date session_id client_id sex birth price categ year month day
79 0_2245 2021-10-04 21:20:27.540982 s_100047 c_8138 f 1984 10.64 0.0 2021 2021-10 2021-10-04
6923 0_2245 2021-03-23 15:57:44.266387 s_10427 c_5869 f 1952 10.64 0.0 2021 2021-03 2021-03-23
8191 0_2245 2021-10-15 09:31:31.539354 s_105069 c_4188 f 1935 10.64 0.0 2021 2021-10 2021-10-15
array(['0.0', '1.0', '2.0'], dtype=object)

Ajout de lignes pour la catégorie 1.0

Solution retenue : Création aléatoire des Ca/jour basée du l'hypothèse que les données ont été récupérées auprès de la comptabilité.

In [23]:
# Création d'un DF pour la catégorie 1.0
Ca_cat1 = Ca_categ_jour.loc[Ca_categ_jour['categ'] == '1.0']
# Vérification des dates manquantes de la catégorie 1.0 sur le mois d'octobre 2021
Ca_cat1_verif=Ca_cat1[(Ca_cat1['jour'] > '2021-09-30') & (Ca_cat1['jour'] < '2021-10-30')]
message = "Vérification des enregistements manquants - du 02/10 au 27/10 2021"
Text_message(message)
Ca_cat1_verif
Vérification des enregistements manquants - du 02/10 au 27/10 2021
Out[23]:
jour categ CA/jour
643 2021-10-01 1.0 6989.80
698 2021-10-28 1.0 6317.99
701 2021-10-29 1.0 6425.18
In [24]:
# Création aléatoire des lignes manquantes 
# On considère que nous avons récupéré les vrais chiffres auprès de la comptabilité
start = dt.date(2021,10,2)
periods = 26
CA = np.random.randint(low=6000, high=8000, size=26).tolist()
for i in range(periods):
    date = (start + dt.timedelta(days = i)).isoformat()
    ligne = pd.DataFrame(data=np.array([[date,'1.0',CA[i]]]), columns=['jour','categ','CA/jour'])
    Ca_categ_jour = pd.concat([Ca_categ_jour,ligne], ignore_index=True)

Ca_categ_jour['jour'] = pd.to_datetime(Ca_categ_jour['jour'])
Ca_categ_jour['CA/jour'] = pd.to_numeric(Ca_categ_jour['CA/jour'])
Ca_categ_jour = Ca_categ_jour.sort_values(by=['jour'], ascending=True)
# Ajout de colonnes mois et année
Ca_categ_jour['year'] = Ca_categ_jour['jour'].dt.year
Ca_categ_jour['month'] = Ca_categ_jour['jour'].dt.strftime('%Y-%m')
#display(Ca_categ_jour.head(3))

message='Mise à jour des données de la catégorie 1.0 - Octobre 2021'
Text_message(message)

CAcat1 = Ca_categ_jour.loc[Ca_categ_jour['categ'] == '1.0']
display(CAcat1.loc[(CAcat1['jour'] > '2021-10-02') & (CAcat1['jour'] < '2021-11-01')].head())

# Modification du DF pour avoir les catégories en colonne
Ca_categ_Grap = Ca_categ_jour.pivot_table(index='jour', columns='categ', 
                                          values="CA/jour",
                                          margins=False).reset_index()

# Modification du DF en vu de la création du graphique
Ca_categ_Grap = Ca_categ_Grap.melt('jour', var_name="var", 
                                   value_name="CA")

# Selection de l'interval 'temps' souhaité
Ca_categ_Grap=Ca_categ_Grap[(Ca_categ_Grap['jour'] > '2021-09-15') & (Ca_categ_Grap['jour'] < '2021-11-15')]

# Génération du Graphique   
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Chiffre d'Affaires par catégorie\ncatégorie 1.0 mise à jour", y=1.05, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

gfg=sns.lineplot(x='jour', y="CA", hue='var', data=Ca_categ_Grap,
                 palette=['lightgrey', 'firebrick','teal'],
                 linewidth=3, legend=True)
legend = gfg.legend(loc='lower right', shadow=True, fontsize='medium', facecolor='whitesmoke', framealpha=0.6)
plt.xticks(rotation=30, fontsize=10)
plt.show()
Mise à jour des données de la catégorie 1.0 - Octobre 2021
jour categ CA/jour year month
2165 2021-10-03 1.0 6502.0 2021 2021-10
2166 2021-10-04 1.0 7385.0 2021 2021-10
2167 2021-10-05 1.0 7526.0 2021 2021-10
2168 2021-10-06 1.0 7755.0 2021 2021-10
2169 2021-10-07 1.0 6515.0 2021 2021-10
No description has been provided for this image

Création d'une colonne tranche d'âge appelée 'Age-range'

In [25]:
# Création d'une colonne tranche d'âge appelée 'Age-range'
transanalyse['Age'] = 2023 - transanalyse['birth']
transanalyse['Age-Range'] = pd.cut(x=transanalyse['Age'], bins=[18,25,35,45,55,65,75,85,95])
transanalyse = transanalyse.sort_values(by=['Age'], ascending=True)
display(transanalyse.head(3))
id_prod date session_id client_id sex birth price categ year month day Age Age-Range
461465 1_376 2022-12-18 05:25:14.679731 s_313790 c_3195 m 2004 17.49 1.0 2022 2022-12 2022-12-18 19 (18, 25]
223590 1_281 2022-05-05 20:50:49.992478 s_204181 c_7712 f 2004 23.99 1.0 2022 2022-05 2022-05-05 19 (18, 25]
223591 2_159 2022-05-05 20:50:51.992478 s_204181 c_7712 f 2004 145.99 2.0 2022 2022-05 2022-05-05 19 (18, 25]

4- Analyses sur le Chiffre d'affaires

4.1 - Chiffres d'affaires total et mensuel sur les deux dernières années

In [26]:
# Calcul du Chiffre d'affaires mensuel sur les deux dernières années
Ca_month = Ca_categ_jour.groupby('month')['CA/jour'].sum().reset_index()
Ca_month = Ca_month.rename(columns={"CA/jour":"CA en millier d'euros"})
Ca_month["CA en millier d'euros"] = round((Ca_month["CA en millier d'euros"]/1000),2)
Ca_month = Ca_month.rename(columns={"month":"Année - Mois"})
catot = Ca_month["CA en millier d'euros"].sum()
Ca_month21 = Ca_month.loc[Ca_month['Année - Mois'] <= '2022-02-01']
Ca_month22 = Ca_month.loc[Ca_month['Année - Mois'] > '2022-02-01']
Ca22 = Ca_month22["CA en millier d'euros"].sum()
Ca21 = Ca_month21["CA en millier d'euros"].sum() 
# Taux de croissance
Taux_croissance = ((Ca22-Ca21)/Ca21)*100
# Affichage des résultats
message = "le chiffre d'affaires total de la librairie en ligne est de {:,.2f} millions d'euros".format(catot)
Text_message(message)
message = "Chiffre d'affaires 2021 (N-1) :    {:,.2f} millions d'euros".format(Ca21)
Text_message(message)
message = "Chiffre d'affaires 2022  (N) :  {:,.2f} millions d'euros".format(Ca22)
Text_message(message)
message = "Taux de croissance N/N-1 : {:,.2f}%".format(Taux_croissance)
Text_message(message)
display(Ca_month.tail(3))

# Génération du Graphique   
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 4))
plt.title("Chiffre d'Affaires mensuel", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
Pro = Ca_month["CA en millier d'euros"].tolist()
for i in range(7):
    plt.text(i-0.2, Pro[i]-25, Pro[i], rotation=90, weight = 'bold', color = 'black')
for i in range(7,24):
    plt.text(i-0.2, Pro[i]-25, Pro[i], rotation=90, weight = 'bold', color = 'white')    
gfg = sns.barplot(x = 'Année - Mois', y = "CA en millier d'euros",
                  data=Ca_month, palette="crest")
gfg = sns.lineplot(data=Ca_month, x='Année - Mois', y="CA en millier d'euros", 
                   color='red')   
gfg.set_ylim(420, 550)
plt.xticks(rotation=45, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("CA en millier d'euros")
plt.xlabel('Mois')
plt.show()
le chiffre d'affaires total de la librairie en ligne est de 12,022.68 millions d'euros
Chiffre d'affaires 2021 (N-1) : 6,008.53 millions d'euros
Chiffre d'affaires 2022 (N) : 6,014.15 millions d'euros
Taux de croissance N/N-1 : 0.09%
Année - Mois CA en millier d'euros
21 2022-12 509.59
22 2023-01 516.78
23 2023-02 456.04
No description has been provided for this image

4.2 - Calcul du panier moyen

In [27]:
# Création d'une copie de la table "transanalyse" 
Client_lastorder = transanalyse.copy()
Client_lastorder = Client_lastorder.sort_values(by='day', ascending=False)

# Table CLients Ca réalisé / Nbr sessions / Panier moyen
Ca_client = Client_lastorder.groupby(['client_id', 'sex', 'Age']).agg(Ca_réalisé=('price', 'sum'),    
        session_count=('session_id', 'count'), session_unique=('session_id', 'nunique')).reset_index()
Ca_client = Ca_client.rename(columns={"Ca_réalisé":"CA réalisé", "sex":"genre",
            "session_count":"Nbr produits achetés", "session_unique":"Nbr sessions"})

Ca_client['Panier moyen par session'] = round((Ca_client['CA réalisé']/Ca_client['Nbr sessions']),2)
Ca_client['Panier moyen par article'] = round((Ca_client['CA réalisé']/Ca_client['Nbr produits achetés']),2)

# Table première commande
old_orders = Client_lastorder.groupby('client_id').last().reset_index()
old_orders = old_orders.sort_values(by='day', ascending=False).reset_index()
# Table contenant les infos utiles
old_orders_infos = old_orders[["client_id","day","price","Age-Range"]]
old_orders_infos = old_orders_infos.rename(columns={"day":"Première commande",
                                                    "price":"Montant 1ère commande",
                                                   "Age-Range":"Tranche d'âge"})

# Table dernière commande
recent_orders = Client_lastorder.groupby('client_id').first().reset_index()
recent_orders = recent_orders.sort_values(by='day', ascending=False).reset_index()
# Tables contenant les infos utiles
recent_orders_infos = recent_orders[["client_id","day","price","birth"]]
recent_orders_infos = recent_orders_infos.rename(columns={"day":"Dernière commande",
                                                          "price":"Montant dernière commande",
                                                         "birth":"Année naissance"})

# Jointure des tables old_orders_infos et recent_orders_infos
Customer_orders = pd.merge(old_orders_infos, recent_orders_infos, on=['client_id'], 
                           how ='left', indicator=True)
# Vérification de la jointure
message = "Customer_orders : {:.0f} jointures différentes de 'both'".format(Customer_orders[Customer_orders['_merge']!='both'].shape[0])
Text_message(message)
# Suppression de la colonne '_merge'
Customer_orders = Customer_orders.drop(['_merge'], axis=1)
display(Customer_orders.tail(3))

# Jointure des tables Ca_client et Customer_orders
Customer_Infos = pd.merge(Ca_client, Customer_orders, on=['client_id'], how ='left', indicator=True)
# Vérification de la jointure
message = "Customer_Infos : {:.0f} jointures différentes de 'both'".format(Customer_Infos[Customer_Infos['_merge']!='both'].shape[0])
Text_message(message)
# Suppression de la colonne '_merge'
Customer_Infos = Customer_Infos.drop(['_merge'], axis=1)
# Mise en ordre des colonnes
colonnes = list(Customer_Infos.columns)
colonnes.insert(3, colonnes.pop(13))
colonnes.insert(4, colonnes.pop(11))
Customer_Infos = Customer_Infos[colonnes]
Customer_Infos
# Ajout des colonnes mois 1ere et derniere commande
Customer_Infos['Première commande'] = pd.to_datetime(Customer_Infos['Première commande'])
Customer_Infos['Dernière commande'] = pd.to_datetime(Customer_Infos['Dernière commande'])
Customer_Infos["Mois 1ere com"] = Customer_Infos["Première commande"].dt.strftime('%B-%Y')
Customer_Infos["Mois Der. com"] = Customer_Infos["Dernière commande"].dt.strftime('%B-%Y')
display(Customer_Infos.tail(3))

# Table Customer_Infos sans les 4 top clients
Customer_Infos_hors4 = Customer_Infos.loc[Customer_Infos["Nbr sessions"] < 1000].reset_index()
Customer_Infos_hors4 = Customer_Infos_hors4.drop(['index'], axis=1)
Customer_orders : 0 jointures différentes de 'both'
client_id Première commande Montant 1ère commande Tranche d'âge Dernière commande Montant dernière commande Année naissance
8597 c_7299 2021-03-01 5.99 (35, 45] 2023-02-28 3.28 1983
8598 c_2851 2021-03-01 17.40 (35, 45] 2023-02-17 12.99 1986
8599 c_6739 2021-03-01 8.80 (45, 55] 2023-02-11 9.45 1969
Customer_Infos : 0 jointures différentes de 'both'
client_id genre Age Année naissance Tranche d'âge CA réalisé Nbr produits achetés Nbr sessions Panier moyen par session Panier moyen par article Première commande Montant 1ère commande Dernière commande Montant dernière commande Mois 1ere com Mois Der. com
8597 c_997 f 29 1994 (25, 35] 1490.01 59 24 62.08 25.25 2021-04-30 42.51 2023-02-05 12.14 April-2021 February-2023
8598 c_998 m 22 2001 (18, 25] 2779.88 53 23 120.86 52.45 2021-03-18 69.99 2023-02-22 181.99 March-2021 February-2023
8599 c_999 m 59 1964 (55, 65] 701.40 46 42 16.70 15.25 2021-07-10 19.50 2023-02-10 19.50 July-2021 February-2023
In [28]:
########################## CA et Panier moyen par Session #################
# Création d'une table en fonction des sessions
Ca_session = transanalyse.groupby(['session_id','client_id','sex','Age'])['price'].sum().reset_index()
Ca_session = Ca_session.sort_values(by='session_id', ascending=False)

# Calcul du panier moyen par session
panier_moyenS = round((Ca_session.price.mean()),2)
# Affichage du panier moyen par session
Left_df = Ca_session.head(3).round(2).astype(str).style.set_table_attributes('style="text-align: left;"')
display(Left_df)
message = "Le panier moyen est de {:.2f} euros par session".format(panier_moyenS)
Text_message(message)

########################## Ca et panier moyen par client #################
# Création de la table Ca client
Customer_Infos_light = Customer_Infos[["client_id","CA réalisé","Nbr sessions",
                   "Panier moyen par session","Nbr produits achetés","Panier moyen par article"]]
Customer_Infos_light = Customer_Infos_light.sort_values(by='CA réalisé', 
                                                        ascending=False).reset_index()
Customer_Infos_light = Customer_Infos_light.drop(['index'], axis=1)
# Affichage du Df
Left_df = Customer_Infos_light.head(3).round(2).astype(str).style.set_table_attributes('style="text-align: left;"')
display(Left_df)
# Calcul du panier moyen par session
panier_moyen_client_session = round((Customer_Infos["Panier moyen par session"].mean()),2)
message = "Le panier moyen client est de {:.2f} euros par session".format(panier_moyen_client_session)
Text_message(message)
# Calcul du panier moyen par article
panier_moyen_client_article = round((Customer_Infos["Panier moyen par article"].mean()),2)
message = "Le panier moyen client est de {:.2f} euros par article".format(panier_moyen_client_article)
Text_message(message)

########################## Graphique panier moyen (sessions) et moyenne mobile #################
Panier_moyen_mois = transanalyse.groupby(['month'])['price'].mean().reset_index()
# Calcul de la moyenne mobile simple
Panier_moyen_mois['MA simple'] = Panier_moyen_mois["price"].rolling(3).mean()
#Panier_moyen_mois.dropna(inplace=True)
# Calcul de la moyenne mobile cumulée
Panier_moyen_mois['MA cumulée'] = Panier_moyen_mois["price"].expanding().mean()
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Evolution panier moyen (Sessions)", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.lineplot(data = Panier_moyen_mois, x="month", y ="price", linewidth=2, color='white',
             label="Panier moyen")
sns.lineplot(data = Panier_moyen_mois, x="month", y ="MA simple", linewidth=4, color='teal',
             label="Moyenne mobile")
sns.lineplot(data = Panier_moyen_mois, x="month", y ="MA cumulée", linewidth=4, color='firebrick',
             label="Moyenne mobile cumulée")
plt.xticks(rotation=45, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Panier moyen en €")
plt.xlabel('Mois')
plt.legend(facecolor='whitesmoke', fontsize=12)
plt.grid(True, color='grey')
plt.show()
  session_id client_id sex Age price
342314 s_99998 c_2795 f 45 35.11
342313 s_99997 c_3521 f 44 6.99
342312 s_99996 c_4900 f 49 56.27
Le panier moyen est de 34.58 euros par session
  client_id CA réalisé Nbr sessions Panier moyen par session Nbr produits achetés Panier moyen par article
0 c_1609 323678.54 10997 29.43 25465 12.71
1 c_4958 288600.82 3851 74.94 5183 55.68
2 c_6714 153417.5 2620 58.56 9174 16.72
Le panier moyen client est de 40.32 euros par session
Le panier moyen client est de 22.02 euros par article
No description has been provided for this image
In [29]:
########################################## Panier moyen #####################################

Customer_Infos_hors4_pn100 = Customer_Infos_hors4.loc[Customer_Infos_hors4["Panier moyen par session"] < 100].reset_index()
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(12, 4)) 
plt.subplot(1,2,1)
plt.title("Panier moyen client par session\nHors Top 4 clients", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

sns.scatterplot(data=Customer_Infos_hors4_pn100, x='Panier moyen par session', y='Nbr sessions', 
                color='teal')
xbar = round((Customer_Infos_hors4['Panier moyen par session'].mean()),2)
xmed = round((Customer_Infos_hors4['Panier moyen par session'].median()),2)
plt.axvline(xbar, color='firebrick', linewidth=2)
plt.axvline(xmed, color='lightgreen', linewidth=2)
plt.legend(['Données',"Moyenne {}".format(xbar),"Médiane {}".format(xmed)], fontsize = 'medium', 
           labelcolor='w')

plt.subplot(1,2,2)
plt.title("Prix moyen par article\nHors Top 4 clients", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

sns.scatterplot(data=Customer_Infos_hors4_pn100, x='Panier moyen par article', y='Nbr sessions', color='teal')
xbar = round((Customer_Infos_hors4['Panier moyen par article'].mean()),2)
xmed = round((Customer_Infos_hors4['Panier moyen par article'].median()),2)
plt.axvline(xbar, color='firebrick', linewidth=2)
plt.axvline(xmed, color='lightgreen', linewidth=2)
plt.legend(['Données',"Moyenne {}".format(xbar),"Médiane {}".format(xmed)], fontsize = 'medium', labelcolor='w')

# Affichage
plt.show()
No description has been provided for this image

4.3 - Analyse sur le turnover clients

In [30]:
########################## Clients n'ayant acheté qu'un seul jour - Nombre de session = 1 #########################
Cust_1session = Customer_Infos.loc[Customer_Infos["Nbr sessions"] == 1].reset_index()
Cust_1session = Cust_1session.drop(['index'], axis=1)
message = "On dénombre {} clients n'ayant acheté qu'une seule fois".format(Cust_1session.shape[0])
Text_message(message)
display(Cust_1session.tail(3))
################################################# NOUVEAUX CLIENTS ##################################################
# Création de compte mensuel 
Creation_compte = Customer_Infos.groupby('Mois 1ere com')['client_id'].count().reset_index()
# Conversion en datetime de la colonne 'Mois 1ere com'
Creation_compte['Mois 1ere com'] = pd.to_datetime(Creation_compte['Mois 1ere com'])
Creation_compte = Creation_compte.rename(columns={"Mois 1ere com":"mois"})
Creation_compte["Mois création"] = Creation_compte["mois"].dt.strftime('%B-%Y')
Creation_compte = Creation_compte.sort_values(by='mois', ascending=True).reset_index()
# Nouveaux clients inscrits à partir de Septembre 2021 - 6 mois après l'ouverture du site
Nouveaux_clients = Creation_compte.loc[Creation_compte["mois"] >= "2021-09-01"]
Nouveaux_clients_ouverture_site = Creation_compte.loc[Creation_compte["mois"] < "2021-09-01"]
Nouveaux_clients = Nouveaux_clients.drop(['index'], axis=1)

################################################# CLIENTS PERDUS ##################################################
# Dernière commande mensuel 
Derniere_com = Customer_Infos.groupby('Mois Der. com')['client_id'].count().reset_index()
# Conversion en datetime de la colonne 'Mois Der. com''
Derniere_com['Mois Der. com'] = pd.to_datetime(Derniere_com['Mois Der. com'])
Derniere_com = Derniere_com.rename(columns={"Mois Der. com":"mois"})
Derniere_com["Mois last com"] = Derniere_com["mois"].dt.strftime('%B-%Y')
Derniere_com = Derniere_com.sort_values(by='mois', ascending=True).reset_index()
# Clients considérés comme perdus : + de 6 mois sans commande
Clients_perdus = Derniere_com.loc[Derniere_com["mois"] <= "2022-08-01"]
Clients_der_com = Derniere_com.loc[Derniere_com["mois"] > "2022-08-01"]
Clients_perdus = Clients_perdus.drop(['index'], axis=1)

################################################# CLIENTS ONE SHOT ##################################################
# Création d'un DF reprenant le Nbr de One Shot par mois
Cust_1session_mois = Cust_1session.copy()
Cust_1session_mois['Première commande'] = pd.to_datetime(Cust_1session_mois['Première commande'])
Cust_1session_mois["Mois One Shot"] = Cust_1session_mois["Première commande"].dt.strftime('%B-%Y')
Cust_1session_mois = Cust_1session_mois.groupby(["Mois 1ere com"])['client_id'].count().reset_index()
Cust_1session_mois = Cust_1session_mois.rename(columns={"Mois 1ere com":"mois"})
Cust_1session_mois['mois'] = pd.to_datetime(Cust_1session_mois['mois'])
Cust_1session_mois["Mois One-Shot"] = Cust_1session_mois['mois'].dt.strftime('%B-%Y')
Cust_1session_mois = Cust_1session_mois.sort_values(by='mois', ascending=True)

##################################### DF avec CLIENTS Nouveaux/Perdus/One shot ############################################
# Jointure des tables 'Clients_perdus' et 'Nouveaux_clients'
Clients_New_lost = pd.merge(Clients_perdus, Nouveaux_clients, on=['mois'], how ='left',
                            indicator=True)
Clients_New_lost = Clients_New_lost.rename(columns={"client_id_x":"clients perdus", 
                                                   "client_id_y":"nouveaux clients"})
Clients_New_lost = Clients_New_lost.drop(['_merge','Mois création'], axis=1)

# Jointure des tables 'Clients_New_lost's et 'Cust_1session_mois'
Clients_New_lost = pd.merge(Clients_New_lost, Cust_1session_mois, on=['mois'], how ='left', indicator=True)
Clients_New_lost = Clients_New_lost.rename(columns={"client_id":"Clients One Shot"})
Clients_New_lost = Clients_New_lost.drop(['_merge','Mois One-Shot'], axis=1)

# graphique
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'white'})
fig = plt.figure(figsize=(12, 5))
ax1 = fig.add_axes([0.15, 0.1, 0.6, 0.8])  # Graphique à gauche 
ax2 = fig.add_axes([0.8, 0.1, 0.5, 0.8])  # Texte à droite
ax1.set_title("Dates d'inscription  et de dernière commande", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.scatterplot(data=Customer_Infos, x='Première commande', y='client_id', color='teal', 
               label='1ère commande', ax=ax1)
sns.scatterplot(data=Customer_Infos, x='Dernière commande', y='client_id', color='firebrick',
               label="Der. commande", ax=ax1)
ax1.set_ylabel("Clients")
ax1.set_xlabel('')
ax1.set_yticks([])
ax1.tick_params(axis='x', rotation=70, labelsize=10)
ax1.legend(loc='upper right', fontsize = 'medium', labelcolor='black', facecolor="whitesmoke")

# Position verticale initiale du texte
y_pos = 1
# Ajouter chaque ligne de texte avec des variables
message = "Nouveaux clients"
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=17, color="firebrick")
y_pos -= 0.1
message = "{:.0f} clients étaient inscrits au 31 Août 2021".format(Nouveaux_clients_ouverture_site["client_id"].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")
y_pos -= 0.08
message = "{:.0f} clients se sont inscrits depuis Septembre 2021".format(Nouveaux_clients["client_id"].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")
y_pos -= 0.2
message = "Clients perdus"
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=17, color="firebrick")
y_pos -= 0.1
message = "{:.0f} clients ont commandé au cours des 6 derniers mois".format(Clients_der_com["client_id"].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")
y_pos -= 0.08
message = "{:.0f} clients considérés comme perdus : aucune commande depuis plus de 6 mois".format(Clients_perdus["client_id"].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")
y_pos -= 0.2
message = "Clients One Shot"
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=17, color="firebrick")
y_pos -= 0.08
message = "{:.0f} clients n'ont commandé qu'une seule fois".format(Cust_1session_mois['client_id'].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")

ax2.axis('off')
plt.show()

fig = plt.figure(figsize=(12, 5))
ax1 = fig.add_axes([0.15, 0.1, 0.6, 0.8])  # Graphique à gauche 
ax2 = fig.add_axes([0.8, 0.1, 0.5, 0.8])  # Texte à droite

ax1.set_title("Nouveaux clients: inscrits après 6 mois d'ouverture\nClients perdus: 6 mois sans commande", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(data=Clients_New_lost, x='Mois last com', y='nouveaux clients', color='teal', alpha=0.9,
               label='1ère commande', ax=ax1)
sns.barplot(data=Clients_New_lost, x='Mois last com', y='clients perdus', color='firebrick', alpha=0.6, 
               label='Dernière commande', ax=ax1)
sns.scatterplot(data=Clients_New_lost, x='Mois last com', y='Clients One Shot', color='white', marker='^',
               label="Nbr One shot", ax=ax1)

val = Clients_New_lost[['mois',"Clients One Shot"]]
val = val.dropna()
val['Clients One Shot'] = val['Clients One Shot'].astype(int)
values = val['Clients One Shot'].to_list()
date = Clients_New_lost['Mois last com'].to_list()

# Ajouter les annotations de texte
for i, value in enumerate(values):
    ax1.text(date[i], value+0.7, str(value), ha='center', va='bottom', color='white', weight = 'bold')

#plt.xticks([])
ax1.legend(loc='upper left', fontsize = 'medium', labelcolor='black',facecolor="whitesmoke")
ax1.set_ylim(0, 80)
ax1.set_ylabel("Nombre Clients")
ax1.set_xlabel('')
ax1.tick_params(axis='x', rotation=70, labelsize=10)

ax2.axis('off')
# Position verticale initiale du texte
y_pos = 1
# Taux d'attrition clients perdus
Cp = Clients_perdus["client_id"].sum()
TAcp = round(((Cp / Customer_Infos.shape[0]) * 100),2)
message = "Taux d'attrition"
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=17, color="firebrick")
y_pos -= 0.1
message = "Le taux d'attrition des clients perdus est de: {:.2f}%".format(TAcp)
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")

y_pos -= 0.2
message = "Stabilité du porte-feuille clients"
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=17, color="firebrick")
y_pos -= 0.1
Nouveaux_clients_2mois = Creation_compte.loc[Creation_compte["mois"] < "2021-05-01"]
message = "Nombre d'inscription sur les deux premiers mois: {:.0f}".format(Nouveaux_clients_2mois["client_id"].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")
y_pos -= 0.08
Clients_der_com_2mois = Derniere_com.loc[Derniere_com["mois"] >= "2023-01-01"]
message = "Nombre de commandes uniques par client sur les deux derniers mois: {:.0f}".format(Clients_der_com_2mois["client_id"].sum())
ax2.text(0, y_pos, message, horizontalalignment='left', verticalalignment='top', fontsize=15, color="teal")
# Affichage du scatterplot
plt.show()
On dénombre 29 clients n'ayant acheté qu'une seule fois
client_id genre Age Année naissance Tranche d'âge CA réalisé Nbr produits achetés Nbr sessions Panier moyen par session Panier moyen par article Première commande Montant 1ère commande Dernière commande Montant dernière commande Mois 1ere com Mois Der. com
26 c_8051 f 31 1992 (25, 35] 110.47 2 1 110.47 55.24 2021-12-01 67.99 2021-12-01 42.48 December-2021 December-2021
27 c_8351 f 55 1968 (45, 55] 6.31 1 1 6.31 6.31 2021-10-26 6.31 2021-10-26 6.31 October-2021 October-2021
28 c_8531 m 20 2003 (18, 25] 27.18 3 1 27.18 9.06 2021-12-23 15.20 2021-12-23 7.99 December-2021 December-2021
No description has been provided for this image
No description has been provided for this image

Observations :

- Il y a très peu de turnover et très peu de One Shot.

- Sur N (Mars22 - Feb23), il n'y a plus de nouveaux clients

- Avec 7144 inscriptions les deux premiers mois et 7085 dernières commandes les deux derniers mois, le portefauille clients est statique et sans développement.

4.4 - Chiffre d'affaires par catégorie

In [31]:
Ca_categ_jour['month'] = pd.to_datetime(Ca_categ_jour['month'], format='%Y-%m')
# Définir les conditions
conditions = [ (Ca_categ_jour['month'] >= '2021-03') & (Ca_categ_jour['month'] <= '2022-02'),
    (Ca_categ_jour['month'] >= '2022-03') & (Ca_categ_jour['month'] <= '2023-02')]
# Définir les valeurs correspondantes
valeurs = ['N-1', 'N']
Ca_categ_jour['Exercice'] = np.select(conditions, valeurs, default='')
# Calcul du Chiffre d'affaires annuel par catégorie
Ca_categ_an = Ca_categ_jour.groupby(['Exercice','categ'])[['CA/jour']].sum().reset_index()
Ca_categ_an = Ca_categ_an.sort_values(by=['Exercice'], ascending=False)
# Changement du nom des colonnes 'CA/jour' par 'CA en million' et 'categ' par 'Catégorie'
Ca_categ_an = Ca_categ_an.rename(columns={'categ': "Catégorie", 
                                            'CA/jour':"CA en million"})
# Ramener le CA en Million
Ca_categ_an["CA en million"] = round((Ca_categ_an["CA en million"]/1000000),3)
Ca_categ_an

# Génération du Graphique CA par catégorie sur 2 ans  
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(15,4)) 
plt.subplot(1,2,1)
plt.title("Chiffre d'Affaires par catégorie sur 2 ans", y=1.05, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
Pro = Ca_par_categ["CA en million"].tolist()
for i in range(3):
    plt.text(Pro[i]-0.7, i+0.05, Pro[i], weight = 'bold', color = 'white')
sns.barplot(x="CA en million",y="Catégorie", data=Ca_par_categ, palette="crest");

# Génération du Graphique CA/Catégorie/an
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.subplot(1,2,2)
# Génération du Graphique CA/Catégorie/an
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.title("CA - Répartion par catégorie/an", y=1.05, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

gfg = sns.barplot(x="Exercice",y="CA en million",  data=Ca_categ_an, hue="Catégorie", palette="crest");
Pro = Ca_categ_an.loc[Ca_categ_an['Catégorie'] == "0.0"]["CA en million"].tolist()
for i in range(2):
    plt.text(i-.35, Pro[i]-0.15, Pro[i], weight = 'bold', color = 'white')
Pro = Ca_categ_an.loc[Ca_categ_an['Catégorie'] == "1.0"]["CA en million"].tolist()
for i in range(2):
    plt.text(i-.09, Pro[i]-0.15, Pro[i], weight = 'bold', color = 'white')  
Pro = Ca_categ_an.loc[Ca_categ_an['Catégorie'] == "2.0"]["CA en million"].tolist()
for i in range(2):
    plt.text(i+0.19, Pro[i]-0.15, Pro[i], weight = 'bold', color = 'white')    

legend = gfg.legend(loc='upper right', shadow=True, fontsize='small', title='Catégorie')
legend.get_frame().set_facecolor('lightgrey')

plt.show()
No description has been provided for this image

4.5 - Chiffre d'affaires réalisé en fonction de l'âge / tranches d'âges

In [32]:
# Calcul du Chiffre d'affaires en fonction de l'age -------------------------------------------
Ca_birthyear= transanalyse.groupby('birth')['price'].sum().reset_index()
Ca_birthyear = Ca_birthyear.sort_values(by=['birth'], ascending=False)
# Changement du nom des colonnes 'price' par 'CA en millier d'eurosn' et 'birth' par 'Année de naissance'
Ca_birthyear = Ca_birthyear.rename(columns={'birth': "Année de naissance", 
                                            'price':"CA en millier d'euros"})
# Ramener le CA en Centaine de Millier
Ca_birthyear["CA en millier d'euros"] = round((Ca_birthyear["CA en millier d'euros"]/1000),2)
# Convertir la 'Catégorie' en objet
Ca_birthyear["Année de naissance"] = Ca_birthyear["Année de naissance"].astype(str)
# Affichage du DF
#display(Ca_birthyear.head(3))

# Calcul du Chiffre d'affaires par tranche d'âge -------------------------------------------
Ca_agerange= transanalyse.groupby('Age-Range')['price'].sum().reset_index()
Ca_agerange = Ca_agerange.sort_values(by=['Age-Range'], ascending=True)
# Changement du nom des colonnes 
Ca_agerange = Ca_agerange.rename(columns={'Age-Range': "Tranche âge", 
                                            'price':"CA en millier d'euros"})
# Ramener le CA en Centaine de Millier
Ca_agerange["CA en millier d'euros"] = round((Ca_agerange["CA en millier d'euros"]/1000),2)

# Génération du Graphique tranche d'âge  ----------------------------------------------------
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Répartition du CA réalisé par tranche d'âges", y=1.01, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
Pro = Ca_agerange["CA en millier d'euros"].tolist()
for i in range(3):
    plt.text(i-.2, Pro[i]-220, Pro[i], weight = 'bold', color = 'black')
for i in range(3,6):
    plt.text(i-.2, Pro[i]-220, Pro[i], weight = 'bold', color = 'white')    
for i in range(6, 8):
    plt.text(i-.2, Pro[i]+50, Pro[i], weight = 'bold', color = 'white')    
sns.barplot(x="Tranche âge",y="CA en millier d'euros", data=Ca_agerange, 
            palette="crest");
No description has been provided for this image
In [33]:
# Génération des Graphiques en fonction de l'age ------------------------------------------------
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
# Graphique tranches d'ages
fig = plt.figure(figsize=(12, 3))
plt.title("Répartition du CA réalisé en fonction de l'âge - Vue d'ensemble", y=1.01, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
Pro = Ca_birthyear["CA en millier d'euros"].tolist()   
gfg = sns.barplot(x="Année de naissance",y="CA en millier d'euros", data=Ca_birthyear, 
            palette="crest");
gfg.set_xticklabels(gfg.get_xticklabels(), rotation=70, fontsize=9) 
# Graphique détaillé ----------------------------------------------------------------------------
fig = plt.figure(figsize=(12, 25))
plt.title("Répartition du CA réalisé en fonction de l'âge - Vue détaillée", y=1.01, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
Pro = Ca_birthyear["CA en millier d'euros"].tolist()
for i in range(20):
    plt.text(Pro[i]-45, i+0.2, Pro[i], weight = 'bold', color = 'black')
for i in range(20, 50):
    plt.text(Pro[i]-45, i+0.2, Pro[i], weight = 'bold', color = 'white')    
for i in range(50, 76):
    plt.text(Pro[i]+5, i+0.2, Pro[i], weight = 'bold', color = 'white')    
gfg = sns.barplot(x="CA en millier d'euros",y="Année de naissance", data=Ca_birthyear, 
            palette="crest");
plt.show()
No description has been provided for this image
No description has been provided for this image

ATTENTION : pour les années 2004, 1999 et 1980, les valeurs de CA semblent déraisonnablement hautes. Effectuer les analyses sur des tranches d'âges permettra de lisser ces valeurs.

4.6 - Moyennes mobiles

In [34]:
# Création de la base reprenant les dates et le Ca réalisé
sns.set(rc={'axes.facecolor':'whitesmoke', 'figure.facecolor':'whitesmoke'})
for i in range(30, 125, 30):
    
    Ca_days = Ca_categ_jour.groupby('jour')['CA/jour'].sum().reset_index()
    Ca_days = Ca_days.rename(columns={"CA/jour":"CA(millier d'euros)"})
    Ca_days["CA(millier d'euros)"] = round((Ca_days["CA(millier d'euros)"]/1000),2)
    MMTGe = Ca_days
    
    # Calcul de la moyenne mobile simple
    MMTGe['MA simple'] = MMTGe["CA(millier d'euros)"].rolling(i).mean()
    MMTGe.dropna(inplace=True)
    # Calcul de la moyenne mobile exponentielle
    MMTGe['MA expo'] = MMTGe["CA(millier d'euros)"].ewm(span=i).mean()    
    # Calcul de la moyenne mobile cumulée
    MMTGe['MA cumulée'] = MMTGe["CA(millier d'euros)"].expanding().mean()

    MMTGe = MMTGe.sort_values(by='jour', ascending=True).reset_index()
    MMTGe = MMTGe.drop('index', axis=1)
   
    # Conversion en DF contenant la date, le type de donnée et sa valeur
    dfm = MMTGe.melt('jour', var_name='Var', value_name='Valeurs')
    #display(dfm.head())
    fig = plt.figure(figsize=(12, 3))
    
    #sns.set_style("whitegrid")
    gfg = sns.lineplot(x='jour', y="Valeurs", hue='Var', data=dfm, 
                       palette=['lightgrey', 'teal', 'cyan', 'firebrick'], linewidth=2, legend=True)
    legend = gfg.legend(loc='upper left', shadow=True, fontsize='small')
    legend.get_frame().set_facecolor('whitesmoke')
    plt.title("Moyenne mobile à %d jours " %(i), y=1.01, 
               fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
    #gfg.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
    gfg.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
    #gfg.tick_params(axis='x', labelrotation=70)
    plt.xticks(rotation=70, fontsize=10)
    plt.yticks(rotation=0, fontsize=10)
    plt.ylabel("Chiffre d'affaires")
    plt.xlabel('')
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

4.7 - Projection du Chiffre d'affaires avec Prophet

In [35]:
# Adaptation du DF pour utiliser 'Prophet'
Ca_jour_actuel = Ca_categ_jour.groupby('jour')['CA/jour'].sum().reset_index()
Data_forecast = Ca_jour_actuel.rename(columns={'jour':'ds','CA/jour':"y"})

m = Prophet(changepoint_prior_scale=0.05)
m.fit(Data_forecast)
future = m.make_future_dataframe(periods=365)
forecast = m.predict(future)

Ca_categ_prevision = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(3)
#fig1 = m.plot(forecast)
#fig2 = m.plot_components(forecast)
plot_plotly(m, forecast)
13:53:09 - cmdstanpy - INFO - Chain [1] start processing
13:53:09 - cmdstanpy - INFO - Chain [1] done processing
In [36]:
plot_components_plotly(m, forecast)

Observations / Points d'intérêts :

- La prévision confirme une légère décroissance au fil du temps que nous avons observée avec les moyennes mobiles.

- Bien qu'il n'y ait quasiment pas de nouveaux clients, les commandes régulières du portefeuille existant assurent un chiffre d'affaire plûtot constant.

5- Analyses sur les variables

5.1 - Variable 'Prix'

In [37]:
################################ Test de Kolmogorov-Smirnov ########################
message = "Test effectué sur la colonne 'price' du fichier 'products'"
Text_message(message)
message = "Test de Kolmogorov-Smirnov "
Text_message(message)
alpha = 0.05
sample_data = products['price'].values
scaler = StandardScaler()
sample_data_scaled = scaler.fit_transform(sample_data.reshape(-1, 1))
# Effectuer le test de Kolmogorov-Smirnov pour vérifier la normalité
kstest_result = kstest(sample_data_scaled.ravel(), 'norm')
# Afficher la statistique de test et la p-value
message = f"Statistique : {kstest_result.statistic:.4f}, P-valeur : {kstest_result.pvalue}"
Text_message(message)
if kstest_result.pvalue > alpha:
    message = "L'échantillon suit une distribution normale (hypothèse nulle non rejetée)"
else:
    message = "L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)"
    
Text_message(message)
Test effectué sur la colonne 'price' du fichier 'products'
Test de Kolmogorov-Smirnov
Statistique : 0.2471, P-valeur : 3.4184614789785306e-177
L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)
In [38]:
message = " ########### Test effectué sur la colonne 'price' du fichier final 'transanalyse' ###########"
Text_message(message)
########################################### test d'Anderson-Darling ################################
# Effectuer le test d'Anderson-Darling
result = anderson(transanalyse['price'])
# Afficher les statistiques de test et les seuils critiques
message = "Test d'Anderson-Darling "
Text_message(message)
message = "Statistique : {:.5f}".format(result.statistic)
Text_message(message)
message = 'Seuils critiques : {}'.format(result.critical_values)
Text_message(message)
message = 'Significativité : {}'.format(result.significance_level)
Text_message(message)

# Interpréter les résultats
for i in range(len(result.critical_values)):
    if result.statistic < result.critical_values[i]:
        a = result.significance_level[i]
        message = "Les données semblent suivre une distribution normale au niveau de significativité {:.f} %".format(a)
        Text_message(message) 
    else:
        a = result.significance_level[i]
        message = "Les données ne suivent pas une distribution normale au niveau de significativité {:.2f} %".format(a)
        Text_message(message)
########### Test effectué sur la colonne 'price' du fichier final 'transanalyse' ###########
Test d'Anderson-Darling
Statistique : 71196.59590
Seuils critiques : [0.576 0.656 0.787 0.918 1.092]
Significativité : [15. 10. 5. 2.5 1. ]
Les données ne suivent pas une distribution normale au niveau de significativité 15.00 %
Les données ne suivent pas une distribution normale au niveau de significativité 10.00 %
Les données ne suivent pas une distribution normale au niveau de significativité 5.00 %
Les données ne suivent pas une distribution normale au niveau de significativité 2.50 %
Les données ne suivent pas une distribution normale au niveau de significativité 1.00 %
In [39]:
########################################### test de Kolmogorov-Smirnov #######################

# Sélection de l'échantillon de données
x= transanalyse['price']
# Standardisation des données
x_standardized = (x - np.mean(x)) / np.std(x)
# Effectuer le test de Kolmogorov-Smirnov
statistic, p_value = st.kstest(x_standardized , 'norm')
# Afficher les résultats du test
alpha = 0.05  # Niveau de signification
message = "Test de Kolmogorov-Smirnov "
Text_message(message)
# autre possibilité : message = "Statistique : {:.5f}".format(statistic)+ ",    " +"P-valeur : {:.3f}".format(p_value)
message = f"Statistique : {statistic:.4f}, P-valeur : {p_value}"
Text_message(message)
if p_value > alpha:
    message = "L'échantillon suit une distribution normale (hypothèse nulle non rejetée)"
    Text_message(message)
else:
    message = "L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)"
    Text_message(message)
    
# Tracer l'histogramme
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(12,3)) 
plt.subplot(1,2,1)
plt.hist(x, range(10,30), density=True, color='teal', alpha=0.7)
# Calculer les valeurs de la densité gaussienne
mu = np.mean(x)
sigma = np.std(x)
x1 = np.linspace(np.min(x), np.max(x), 100)
gaussian = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x1 - mu) / sigma) ** 2)
# Tracer la Moyenne empirique
xbar=np.mean(transanalyse['price'])
plt.axvline(xbar, color='orange', linewidth=2)
# Tracer la courbe de densité gaussienne
plt.plot(x1, gaussian, color='red', linewidth=2)
plt.xlim([0,40])
plt.ylabel('Fréquence')
plt.title('Histogramme de l\'échantillon')
plt.grid(color='grey', linestyle='-', linewidth=1)
# Ajouter une légende et des étiquettes d'axes
plt.legend(['Moyenne empirique', 'Densité gaussienne', 'Histogramme'], fontsize = 'small', 
           labelcolor='w')
plt.ylabel('Fréquence')
plt.xlabel('Prix')
plt.title("Variable 'Prix' - Distribution", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

plt.subplot(1,2,2)
# Tracer le graphique quantile-quantile (Q-Q plot)
st.probplot(x1, dist='norm', plot=plt)
plt.plot(plt.gca().lines[0].get_xdata(), plt.gca().lines[0].get_ydata(),marker='o', 
         markerfacecolor='black', markeredgecolor='teal', linestyle='none')
plt.xlabel('Quantiles théoriques (distribution normale)')
plt.ylabel('Quantiles observés')
plt.grid(color='grey', linestyle='-', linewidth=1)
plt.title("Graphique quantile-quantile (Q-Q plot)", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
        
plt.show()
Test de Kolmogorov-Smirnov
Statistique : 0.2320, P-valeur : 0.0
L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)
No description has been provided for this image

5.2 - Variable 'Age'

In [40]:
################################ Test de Kolmogorov-Smirnov ########################
message = "Test effectué sur la colonne 'birth' du fichier 'customers'"
Text_message(message)
message = "Test de Kolmogorov-Smirnov "
Text_message(message)
sample_data = customers['birth'].values
scaler = StandardScaler()
sample_data_scaled = scaler.fit_transform(sample_data.reshape(-1, 1))
# Effectuer le test de Kolmogorov-Smirnov pour vérifier la normalité
kstest_result = kstest(sample_data_scaled.ravel(), 'norm')
alpha= 0.05
# Afficher la statistique de test et la p-value
message = f"Statistique : {kstest_result.statistic:.4f}, P-valeur : {kstest_result.pvalue}"
Text_message(message)
if kstest_result.pvalue > alpha:
    message = "L'échantillon suit une distribution normale (hypothèse nulle non rejetée)"
    Text_message(message)
else:
    message = "L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)"
    Text_message(message)
Test effectué sur la colonne 'birth' du fichier 'customers'
Test de Kolmogorov-Smirnov
Statistique : 0.0642, P-valeur : 2.248363172481548e-31
L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)
In [41]:
message = " ########### Test effectué sur la colonne 'Age' du fichier final 'transanalyse' ###########"
Text_message(message)
########################################### test d'Anderson-Darling ################################
# Effectuer le test d'Anderson-Darling
result = anderson(transanalyse['Age'])
p_value = 1.0 / np.max(result.critical_values)
#p_value = min(result.critical_values) / 100
# Afficher les statistiques de test et les seuils critiques
message = "Test d'Anderson-Darling "
Text_message(message)
message = "Statistique : {:.5f}".format(result.statistic)
Text_message(message)
message = 'Seuils critiques : {}'.format(result.critical_values)
Text_message(message)
message = 'Significativité : {}'.format(result.significance_level)
Text_message(message)

# Interpréter les résultats
for i in range(len(result.critical_values)):
    if result.statistic < result.critical_values[i]:
        a = result.significance_level[i]
        message = "Les données semblent suivre une distribution normale au niveau de significativité {:.f} %".format(a)
        Text_message(message)        
    else:
        a = result.significance_level[i]
        message = "Les données ne suivent pas une distribution normale au niveau de significativité {:.2f} %".format(a)
        Text_message(message) 

########################################### test de Kolmogorov-Smirnov #######################

# Données à observer
data = transanalyse['Age']
# Standardisation des données
data_standardized = (data - np.mean(data)) / np.std(data)
# Effectuer le test de Kolmogorov-Smirnov
statistic, p_value = st.kstest(data_standardized , 'norm')
# Afficher les résultats du test
alpha = 0.05  # Niveau de signification
message = "Test de Kolmogorov-Smirnov "
Text_message(message)
message = "Statistique : {:.5f}".format(statistic)
Text_message(message)
message = "P-valeur : {:.3f}".format(p_value)
Text_message(message)
if p_value > alpha:
    message = "L'échantillon suit une distribution normale (hypothèse nulle non rejetée)"
    Text_message(message)
else:
    message = "L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)"
    Text_message(message)

# Tracer l'histogramme
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(12,3)) 
plt.subplot(1,2,1)
plt.hist(data, range(19,95), density=True, color='teal', alpha=0.7)
# Calculer les valeurs de la densité gaussienne
# Paramètres de la distribution gaussienne
mu = np.mean(data)
sigma = np.std(data)
x1 = np.linspace(np.min(data), np.max(data), 100)
gaussian = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x1 - mu) / sigma) ** 2)
# Tracer la Moyenne empirique
xbar=np.mean(transanalyse['Age'])
plt.axvline(xbar, color='orange', linewidth=2)
# Tracer la courbe de densité gaussienne
plt.plot(x1, gaussian, color='red', linewidth=2)
plt.grid(color='w', linestyle='-', linewidth=1)
plt.xlim([19,95])
# Ajouter une légende et des étiquettes d'axes
plt.legend(['Moyenne empirique', 'Densité gaussienne', 'Histogramme'], fontsize = 'medium', 
           labelcolor='w')
plt.xlabel('Valeurs')
plt.ylabel('Densité')
plt.title("Variable 'Age' - Distribution", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

plt.subplot(1,2,2)
# Tracer le graphique quantile-quantile (Q-Q plot)
st.probplot(x1, dist='norm', plot=plt)
plt.plot(plt.gca().lines[0].get_xdata(), plt.gca().lines[0].get_ydata(),
         marker='o', markerfacecolor='black', markeredgecolor='teal', linestyle='None')
plt.xlabel('Quantiles théoriques (distribution normale)')
plt.ylabel('Quantiles observés')
plt.title("Graphique quantile-quantile (Q-Q plot)", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
# Afficher le graphique
plt.show()
########### Test effectué sur la colonne 'Age' du fichier final 'transanalyse' ###########
Test d'Anderson-Darling
Statistique : 5025.22822
Seuils critiques : [0.576 0.656 0.787 0.918 1.092]
Significativité : [15. 10. 5. 2.5 1. ]
Les données ne suivent pas une distribution normale au niveau de significativité 15.00 %
Les données ne suivent pas une distribution normale au niveau de significativité 10.00 %
Les données ne suivent pas une distribution normale au niveau de significativité 5.00 %
Les données ne suivent pas une distribution normale au niveau de significativité 2.50 %
Les données ne suivent pas une distribution normale au niveau de significativité 1.00 %
Test de Kolmogorov-Smirnov
Statistique : 0.08186
P-valeur : 0.000
L'échantillon ne suit pas une distribution normale (hypothèse nulle rejetée)
No description has been provided for this image

5.3 - Comparaison de distribution sur les groupes 'Femmes'/'Hommes'

In [42]:
# Créer la table pivotante en utilisant 'sum' comme aggfunc
Genre_achats = pd.pivot_table(transanalyse, index='Age', columns='sex', values='price', aggfunc='sum')
Genre_achats = Genre_achats.reset_index()
Genre_achat1 = transanalyse.groupby(['Age', 'sex'])['price'].sum().reset_index()
In [43]:
## Tests de normalité et d'égalité des variances pour vérifier les hypophèses à l'utilisation ou non d'un test de student ##
# Echantillons 
x = Genre_achats['f']
y = Genre_achats['m']
from scipy.stats import f_oneway
########################### Test de Shapiro-Wilk pour tester la normalité des variances #####################
message = 'Test de Shapiro-Wilk'
Text_message(message)
statistic, p_value = st.shapiro(x)
message = "Statistique de test : {:.5f}  et P_value : {}".format(statistic,p_value)
Text_message(message)
# Interprétation des résultats
if p_value > 0.05:
    message = "L'échantillon 'Femmes' suit une distribution normale."
else:
    message = "L'échantillon 'Femmes' ne suit pas une distribution normale."
Text_message(message)

statistic1, p_value1 = st.shapiro(y)
message = "Statistique de test : {:.5f}  et P_value : {}".format(statistic1,p_value1)
Text_message(message)
# Interprétation des résultats
if p_value > 0.05:
    message = "L'échantillon 'Hommes' suit une distribution normale."
else:
    message = "L'échantillon 'Hommes' ne suit pas une distribution normale."
Text_message(message)
message = "Les deux groupes ne suivent pas une distribution normale. Un test non paramétrique est utilisé à la place du test de Student"
Text_message(message)
message ="------------------------------------------------------------------------"
Text_message(message)
###########################  Test de Wilcoxon-Mann-Whitney pour comparer la distribution des échantillons ##################
message = 'Test de Wilcoxon-Mann-Whitney'
Text_message(message)
# Exemple d'échantillons indépendants (vous devez remplacer ces échantillons par vos propres données)
echantillon1 = Genre_achats['f']
echantillon2 = Genre_achats['m']

# Effectuer le test de Wilcoxon-Mann-Whitney
statistique, p_value = mannwhitneyu(echantillon1, echantillon2)
# Afficher les résultats du test
message = "Test Statistique U: {}".format(statistique)
Text_message(message)
message = "P-value : {}".format(p_value)
Text_message(message)
# Interpréter le résultat du test
alpha = 0.05  # Niveau de signification (généralement 0,05)
if p_value < alpha:
    message = "Il y a une différence statistiquement significative entre les distributions."
else:
    message = "Il n'y a pas de différence statistiquement significative entre les distributions.(hypothèse nulle non rejetée)"
Text_message(message)
# Graphique 
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Volume d'achats suivant l'âge par genre", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.boxplot(x='sex', y="price", data=Genre_achat1, palette='crest', showfliers=False,showmeans=True,
            medianprops=dict(linestyle='-', linewidth=2, color='coral'), notch = True,
            meanprops=dict(marker='p',markerfacecolor='white', markeredgecolor='black', markersize=8),
            boxprops = dict(linestyle='-', linewidth=0))
plt.ylabel("Volumes d'achats")
plt.xlabel('Genre')
plt.show()    
Test de Shapiro-Wilk
Statistique de test : 0.91517 et P_value : 8.590931247454137e-05
L'échantillon 'Femmes' ne suit pas une distribution normale.
Statistique de test : 0.77113 et P_value : 1.5544819875756843e-09
L'échantillon 'Hommes' ne suit pas une distribution normale.
Les deux groupes ne suivent pas une distribution normale. Un test non paramétrique est utilisé à la
place du test de Student
------------------------------------------------------------------------
Test de Wilcoxon-Mann-Whitney
Test Statistique U: 3015.0
P-value : 0.6411110918542542
Il n'y a pas de différence statistiquement significative entre les distributions.(hypothèse nulle
non rejetée)
No description has been provided for this image

5.4 - Mesures de tendance centrale sur les catégories

In [44]:
# Données pour les graphiques
data0 = transanalyse.loc[transanalyse.categ == '0.0', :]
data1 = transanalyse.loc[transanalyse.categ == '1.0', :]
data2 = transanalyse.loc[transanalyse.categ == '2.0', :]

# Listes de données et de labels
datas = [data0['price'], data1['price'], data2['price']]
labels = ['Categorie 0.0', 'Categorie 1.0', 'Categorie 2.0']

# Création de la figure et des sous-graphiques
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(12, 3))
# Boucle for avec histplot, médiane, moyenne et mode
for data, label, ax in zip(datas, labels, axs):
    data.hist(ax=ax, color='teal')
    ax.set_title(label, y=1.01, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
    mediane = np.median(data)
    moyenne = np.mean(data)
    mode = data.mode()
    ax.axvline(mediane, color='r', linestyle='-', linewidth=2, label=f"Médiane: {mediane:.2f}")
    ax.axvline(moyenne, color='y', linestyle='--', linewidth=2, label=f"Moyenne: {moyenne:.2f}")
    barres = ax.patches
    hauteurs = max([barre.get_height() for barre in barres])
    ax.text(moyenne*2, hauteurs/1.5, f'Mode : {mode.values}', color="white",
         fontsize=12, bbox=dict(facecolor='black', alpha=0.8, edgecolor='none', boxstyle='round'))
    ax.legend(fontsize = 'medium', labelcolor='w')
# Ajustement de l'espacement entre les graphiques
plt.tight_layout()
# Affichage de la figure
plt.show()
No description has been provided for this image

6- Etude sur les références / catégories

6.1 - Distribution empirique des catégories

In [45]:
# Création d'un Df comprenant les effectifs (hommes/femmes) par catégorie
Compt_cat = transanalyse.pivot_table(index='categ', columns='sex', values='id_prod',aggfunc=len,
                               margins=False).reset_index()
# Ajout de la colonne 'Total'
Compt_cat["Total"] = Compt_cat["f"] + Compt_cat["m"]
# Ajout de la fréquence des effectifs
Compt_cat["fréquence"] = round((Compt_cat["Total"] / len(transanalyse)),3)
# Affichage du tableau
#display(Compt_cat.head())

# Diagramme en secteurs
plt.figure(figsize=(12,3)) 
plt.subplot(1,2,1)
colors = sns.color_palette('crest')
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'black'})
# Diagramme en secteurs
labels = ['Categ 0.0', 'Categ 1.0', 'Categ 2.0']
plt.title("Distribution empirique - Catégories", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'}) 
plt.pie(Compt_cat['fréquence'], colors = colors, labels=labels,autopct = '%.2f%%',
       explode = [0.05,0.05,0.05], shadow = 'True',
       textprops = {'color': 'black','fontsize':10},
       wedgeprops = {'linewidth': 2})
# Forcer le pie chart en cercle plutôt qu'en éllipse
plt.axis('equal') 
# Diagramme en tuyaux d'orgues
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.subplot(1,2,2) 
plt.title("Distribution empirique - Catégories", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x="categ",y="fréquence", data=Compt_cat, width=0.3,
            palette="crest")
plt.xlabel('Catégorie')
# Affiche les graphiques
plt.show()
No description has been provided for this image

6.2 - Courbes de Pareto sur les catégories

In [46]:
################# Courbe PARETO - Quantités ####################
pareto = transanalyse.groupby(['categ'])['session_id'].count().reset_index()
# Changement du nom des colonnes 'session_id' par 'Quantité' et 'categ' par 'Catégorie'
pareto = pareto.rename(columns={'session_id': "Quantité", "categ":"catégorie"})
# Calculer les pourcentages cumulés
pareto['Pourcentage'] = (pareto['Quantité'] / pareto['Quantité'].sum()) * 100
pareto['Pourcentage_cumulé'] = pareto['Pourcentage'].cumsum()
################# Courbe PARETO - Chiffre d'affaires ####################
paretop = transanalyse.groupby(['categ'])['price'].sum().reset_index()
paretop['price'] = paretop['price'] / 1000
# Changement noms colonnes 'price' par 'Chiffre d'affaires (en millier)' et 'categ' par 'Catégorie'
paretop = paretop.rename(columns={'price': "Chiffre d'affaires (en millier)", "categ":"catégorie"})
# Calculer les pourcentages cumulés
paretop['Pourcentage'] = (paretop["Chiffre d'affaires (en millier)"] / 
                          paretop["Chiffre d'affaires (en millier)"].sum()) * 100
paretop['Pourcentage_cumulé'] = paretop['Pourcentage'].cumsum()

# Tracer les courbes de Pareto Quantités
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))
sns.barplot(x='catégorie', y='Quantité', data=pareto, palette='crest', ax=ax1)
# Tracer la courbe de Pareto (pourcentage cumulé)
ax1twin = ax1.twinx()
sns.lineplot(x='catégorie', y='Pourcentage_cumulé', data=pareto, marker='o', color='salmon', 
             ax=ax1twin)
#ax1.set_title('Courbe de Pareto des catégories (Quantités)')
ax1.set_title("Courbe de Pareto des catégories (Quantités)", y=1.01, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
# Tracer les courbes de Pareto Chiffre d'affaires
sns.barplot(x='catégorie', y="Chiffre d'affaires (en millier)", data=paretop, palette='crest', ax=ax2)
# Tracer la courbe de Pareto (pourcentage cumulé)
ax2twin = ax2.twinx()
sns.lineplot(x='catégorie', y='Pourcentage_cumulé', data=paretop, marker='o', color='salmon', 
             ax=ax2twin)
#ax2.set_title("Courbe de Pareto des catégories (Chiffre d'affaires)")
ax2.set_title("Courbe de Pareto des catégories (Chiffre d'affaires)", y=1.01, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
ax1twin.grid(False)
ax2twin.grid(False)
plt.tight_layout()
plt.show()

############## Création des Dataframes utiles à la réalisation des courbes de PARETO  ##############
Cat0 = transanalyse.loc[transanalyse['categ'] == '0.0']
Cat1 = transanalyse.loc[transanalyse['categ'] == '1.0']
Cat2 = transanalyse.loc[transanalyse['categ'] == '2.0']

pareto0 = Cat0.groupby(['id_prod'])['session_id'].count().reset_index()
# Changement du nom des colonnes 'session_id' par 'Quantité' et 'categ' par 'Catégorie'
pareto0 = pareto0.rename(columns={'session_id': "Ventes", "id_prod":"produit"})
# Calculer les pourcentages cumulés
pareto0['Pourcentage'] = (pareto0['Ventes'] / pareto0['Ventes'].sum()) * 100
pareto0 = pareto0.sort_values(by='Pourcentage', ascending=False).reset_index()
pareto0 = pareto0.drop(['index'], axis=1)
pareto0['Pourcentage_cumulé'] = pareto0['Pourcentage'].cumsum()

pareto1 = Cat1.groupby(['id_prod'])['session_id'].count().reset_index()
# Changement du nom des colonnes 'session_id' par 'Quantité' et 'categ' par 'Catégorie'
pareto1 = pareto1.rename(columns={'session_id': "Ventes", "id_prod":"produit"})
# Calculer les pourcentages cumulés
pareto1['Pourcentage'] = (pareto1['Ventes'] / pareto1['Ventes'].sum()) * 100
pareto1 = pareto1.sort_values(by='Pourcentage', ascending=False).reset_index()
pareto1 = pareto1.drop(['index'], axis=1)
pareto1['Pourcentage_cumulé'] = pareto1['Pourcentage'].cumsum()

pareto2 = Cat2.groupby(['id_prod'])['session_id'].count().reset_index()
# Changement du nom des colonnes 'session_id' par 'Quantité' et 'categ' par 'Catégorie'
pareto2 = pareto2.rename(columns={'session_id': "Ventes", "id_prod":"produit"})
# Calculer les pourcentages cumulés
pareto2['Pourcentage'] = (pareto2['Ventes'] / pareto2['Ventes'].sum()) * 100
#pareto2 = pareto2.drop(['index'], axis=1)
pareto2 = pareto2.sort_values(by='Pourcentage', ascending=False).reset_index()
pareto2 = pareto2.drop(['index'], axis=1)
pareto2['Pourcentage_cumulé'] = pareto2['Pourcentage'].cumsum()
################################################################################################

dataframes= [pareto0, pareto1, pareto2]
for i, pareto in enumerate(dataframes, start=0):
    seuil_ventes(pareto)
    Comp = seuil_ventes(pareto)[0]
    pour = seuil_ventes(pareto)[1]
    seuil = seuil_ventes(pareto)[2]
    tot = seuil_ventes(pareto)[3]
    # Tracer les courbes de Pareto Quantités
    sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
    fig, ax1 = plt.subplots(figsize=(10, 2))
    ax2 = ax1.twinx()
    plt.title("Courbe de Pareto de la catégorie  {}.0 \nVolumes de ventes".format(i), y=1.05, 
              fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
    sns.barplot(x='produit', y='Ventes', data=pareto, color='black', ax=ax1)
    # Tracer la courbe de Pareto (pourcentage cumulé)
    sns.lineplot(x='produit', y='Pourcentage_cumulé', data=pareto, color='firebrick', linewidth=2,
                 markeredgecolor = 'none', ax=ax2)
    ax2.axhline(y=pour, color='teal', linestyle='-', linewidth=2 , label='77% des ventes')
    # Ligne verticale pour indiquer le produit
    ax1.axvline(x=seuil, color='springgreen', linestyle='--', linewidth=2 , label='Produit indiqué')
    
    # Affichage de l'indice de Gini
    ax2.text(seuil*2.8, pour-70, 
         f'{pour}% des ventes concerne {100-pour}% des produits\nsoit {seuil} produits sur {tot} ',
         fontsize=10, bbox=dict(facecolor='whitesmoke', edgecolor='gray', boxstyle='round'))
    
    ax2.text(seuil/1.3, pour-70,
             f'{seuil} produits', color='black',
         fontsize=10, bbox=dict(facecolor='springgreen', edgecolor='gray', boxstyle='round'))
    
    ax2.set_ylabel('Pourcentage cumulé', color='r')
    plt.legend(loc='center right', shadow=True, fontsize='small', title='', facecolor='whitesmoke')
    plt.xticks([])
    ax1.set_xlabel('Livres de la catégorie')
    ax1.grid(False)
    ax2.grid(False)
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

6.3 - Quantités de livres vendus : Top et Flop des catégories

In [47]:
# Préparation des données
Bestsell = transanalyse.groupby(['id_prod','categ','price']).size().reset_index()
Bestsell = Bestsell.rename(columns={0: "Qt vendus"})
Bestsell = Bestsell.sort_values(by='Qt vendus', ascending=False)
Bests = Bestsell.head(20)
Worths = Bestsell.tail(20)

Bestsell00 = Bestsell.loc[Bestsell['categ'] == '0.0']
Bestsell02 = Bestsell.loc[Bestsell['categ'] == '2.0']
Bests00 = Bestsell00.head(20)
Bests02 = Bestsell02.head(20)

# Génération du Graphique  TOP 
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'whitesmoke'})
fig = plt.figure(figsize=(12, 3))
plt.subplot(1,2,1)
plt.title("Meilleures ventes toutes catégories", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})  
gfg = sns.barplot(x="id_prod",y="Qt vendus", data=Bests, hue='categ',
            palette="rocket");
legend = gfg.legend(loc='upper right', shadow=True, fontsize='small', title='Catégorie')
legend.get_frame().set_facecolor('lightgrey')
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Quantité vendue")
plt.xlabel('Code livre')   
gfg.set_ylim(1700, 2300)
# Génération du Graphique   FLOP
plt.subplot(1,2,2)
plt.title("Flop 20 toutes catégories", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})  
gfg = sns.barplot(x="id_prod",y="Qt vendus", data=Worths, hue='categ',
            palette="rocket");
legend = gfg.legend(loc='upper right', shadow=True, fontsize='small', title='Catégorie')
legend.get_frame().set_facecolor('lightgrey')
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Quantité vendue")
plt.xlabel('Code livre')   
plt.show()

# Génération du Graphique  TOP 2.0
fig = plt.figure(figsize=(12, 3))
plt.subplot(1,2,1)
plt.title("Meilleures ventes catégorie 2.0", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})  
gfg = sns.barplot(x="id_prod",y="Qt vendus", data=Bests02, hue='categ',
            palette="crest");
legend = gfg.legend(loc='upper right', shadow=True, fontsize='small', title='Catégorie')
legend.get_frame().set_facecolor('lightgrey')
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Quantité vendue")
plt.xlabel('Code livre')
gfg.set_ylim(500, 1100)

# Génération du Graphique  TOP 0.0
plt.subplot(1,2,2)
plt.title("Meilleures ventes catégorie 0.0", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})  
gfg = sns.barplot(x="id_prod",y="Qt vendus", data=Bests00, hue='categ',
            palette="flare");
legend = gfg.legend(loc='upper right', shadow=True, fontsize='small', title='Catégorie')
legend.get_frame().set_facecolor('lightgrey')
gfg.set_ylim(1150, 1300)
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Quantité vendue")
plt.xlabel('Code livre')
plt.show()
No description has been provided for this image
No description has been provided for this image

6.4 - Top Chiffres d'Affaires toutes catégories

In [48]:
Bestsell['Ca/livre'] = Bestsell['price'] * Bestsell['Qt vendus']
Bestsell = Bestsell.sort_values(by='Ca/livre', ascending=False)
BestCA = Bestsell.head(20)

# Génération du Graphique  TOP
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'whitesmoke'})
fig = plt.figure(figsize=(12, 3))
plt.title("Meilleurs CA toutes catégories", y=1.01, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})  
gfg = sns.barplot(x="id_prod",y="Ca/livre", data=BestCA, hue='categ',
            palette="rocket");
legend = gfg.legend(loc='upper right', shadow=True, fontsize='small', title='Catégorie')
legend.get_frame().set_facecolor('lightgrey')
gfg.set_ylim(40000, 100000)
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Chiffre d'affaires")
plt.xlabel('Code livre')
plt.show()
No description has been provided for this image

7- Analyses quantitatives comportementales - Profil clientèle - Répartition du CA

7.1 - Distribution empirique des ages

In [49]:
# Création d'un Df comprenant les effectifs (hommes/femmes) par catégorie
Compt_age = transanalyse.pivot_table(index='Age-Range', columns='sex', values='id_prod',aggfunc=len,
                               margins=False).reset_index()

Compt_age["Total"] = Compt_age["f"] + Compt_age["m"]
# Ajout de la fréquence des effectifs
Compt_age["fréquence"] = round((Compt_age["Total"] / len(transanalyse)),3)
# Affichage du tableau 
display(Compt_age)

# Création des graphiques
plt.figure(figsize=(12,4))
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'whitesmoke'})
plt.subplot(1,2,1)
colors = sns.color_palette('crest')
# Diagramme en secteurs
#labels = Compt_age['Age-Range'].to_list
plt.title("Distribution empirique - Tranches d'âges", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'}) 
plt.pie(Compt_age['fréquence'], colors = colors, autopct = '%.2f%%', shadow = 'True', 
        explode = [0.1,0.05,0.05,0.05,0.05,0.1,0.2,0.4], labels=Compt_age['Age-Range'],
        textprops = {'color': 'black','fontsize':10},
       wedgeprops = {'linewidth': 2})
# Forcer le pie chart en cercle plutôt qu'en éllipse
plt.axis('equal') 
plt.subplot(1,2,2)
# Diagramme en tuyaux d'orgues
plt.title("Distribution empirique - Tranches d'âges", y=1.01, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
gfg = sns.barplot(x="Age-Range",y="fréquence", data=Compt_age, width=0.4,
            palette="crest");
gfg.set_xticklabels(gfg.get_xticklabels(), rotation=45)
plt.ylabel("")
plt.xlabel("Tranches d'âges")
# Affiche les graphiques
plt.show()
sex Age-Range f m Total fréquence
0 (18, 25] 21802 25968 47770 0.070
1 (25, 35] 54966 52968 107934 0.159
2 (35, 45] 106512 128739 235251 0.347
3 (45, 55] 87063 71783 158846 0.234
4 (55, 65] 32921 32957 65878 0.097
5 (65, 75] 24152 19457 43609 0.064
6 (75, 85] 7886 7572 15458 0.023
7 (85, 95] 2681 1078 3759 0.006
No description has been provided for this image

7.2 - Analyse du panier moyen par tranche d'âges et nombre de sessions

In [50]:
# Fixer les tranches d'ages pour l'analyse
Tranches = transanalyse.copy()
age1 = 31
age2 = 51
Tranches['Age'] = 2023 - Tranches['birth']
Tranches['Age-Range'] = pd.cut(x=Tranches['Age'], bins=[18,age1,age2,95])
Tranches = Tranches.sort_values(by=['Age'], ascending=True)

# Création du Dataframe utile à l'analyse
Corr_age_somme1 = transanalyse.groupby('Age-Range').agg({'price': 'sum', 
                                                        'session_id': 'count'}).reset_index()
Corr_age_somme1['price'] = round((Corr_age_somme1['price']),2)
# Création du Dataframe utile à l'analyse
Corr_age_somme = Tranches.groupby('Age-Range').agg({'price': 'sum', 
                                                        'session_id': 'count'}).reset_index() 
Corr_age_somme['price'] = round((Corr_age_somme['price']),2)
# Préparation des attributs du boxplot
medianprops = dict(linestyle='-', linewidth=2, color='coral')
meanprops = dict(marker='p',markerfacecolor='firebrick', markeredgecolor='black', markersize=8)
boxprops = dict(linestyle='-', linewidth=0, facecolor='teal')


# Renommer les colonnes des Df utilisés
Corr_age_somme2 = Corr_age_somme
Corr_age_somme2 = Corr_age_somme2.rename(columns={'Age-Range': "Tranche d'âges", 
                                                  'price': "Montant d'achat", 
                                                  "session_id":"Nbr de sessions"})
Corr_age_somme1 = Corr_age_somme1.rename(columns={'Age-Range': "Tranche d'âges", 
                                                  'price': "Montant d'achat", 
                                                  "session_id":"Nbr de sessions"})

# Création d'une figure avec deux sous-graphiques
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'whitesmoke'})
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax3 = ax2.twinx()
# Affichage du dataframe sur le premier sous-graphique
ax1.axis('off')  # Supprime les axes
ax1.table(cellText=Corr_age_somme2.values, colLabels=Corr_age_somme2.columns, loc='top',
         cellLoc='right', cellColours=[['lightgray', 'whitesmoke', 'whitesmoke']]*len(Corr_age_somme2),
         bbox=[0, 0, 0.9, 0.8])
# Tracer le premier graphique Boxplot
plt.suptitle("Panier moyen et nombre d'achats par tranche d'âge", y=1, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

sns.boxplot(x="Age-Range", y="price", data=transanalyse, ax=ax2, showfliers=False, showmeans=True,
            notch = True, meanprops=meanprops, medianprops=medianprops,
            boxprops = boxprops)
ax2.set_xlabel("Tranche d'âge")
ax2.set_ylabel("Panier moyen", color='teal')  
# Tracer le deuxième graphique Barplot
sns.barplot(x="Tranche d'âges", y='Nbr de sessions', data=Corr_age_somme2, ax=ax3,  palette='Reds', alpha=0.4,
            edgecolor="red", linewidth=2)
ax3.set_ylabel("Nombre de sessions", color='salmon')
ax3.grid(False)
plt.show()


# Création d'une figure avec deux sous-graphiques
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax3 = ax2.twinx()
# Affichage du dataframe sur le premier sous-graphique
ax1.axis('off')  # Supprime les axes
ax1.table(cellText=Corr_age_somme1.values, colLabels=Corr_age_somme1.columns, loc='top',
         cellLoc='right', cellColours=[['lightgray', 'whitesmoke', 'whitesmoke']]*len(Corr_age_somme1),
         bbox=[0, 0, 0.9, 0.8])

# Tracé d'un graphique (ici, un diagramme en barres) sur le deuxième sous-graphique
plt.suptitle("Tranches d'âges affinées\nPanier moyen et nombre d'achats par tranche d'âge", y=1, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.boxplot(x="Age-Range", y="price", data=transanalyse, ax=ax2, showfliers=False, showmeans=True,
            notch = True, meanprops=meanprops, medianprops=medianprops, 
            boxprops = boxprops)
ax2.set_xlabel("Tranche d'âge")
ax2.set_ylabel("Panier moyen", color='teal')  
# Tracer le deuxième graphique Barplot
sns.barplot(x="Tranche d'âges", y='Nbr de sessions', data=Corr_age_somme1, ax=ax3, palette='Reds', alpha=0.4,
            edgecolor="red", linewidth=2)
ax3.set_ylabel("Nombre de sessions", color='salmon') 
ax2.set_xticklabels(ax2.get_xticklabels(), rotation=30) 
ax3.grid(False)
plt.show()
No description has been provided for this image
No description has been provided for this image

Ces 2 graphiques nous donnent des informations très intéressantes :

- les jeunes commandent peu mais achètent des livres chers / collectors / reliés pour offrir par exemple. Nous observons une importante dispersion du montant d'achat.

- les 30-50 ans commandent le plus et achètent des livres correspondant à une consommation de livres de poche plus régulière

- Après 55 ans, les ventes diminuent mais le panier moyen reste stable

- On distingue clairement l'achat plaisir où la moyenne s'éloigne de la médiane et l'achat récurrent où médiane et moyenne sont équivalentes

7.3 - Analyse sur les sessions

In [51]:
##################### Visualisation du nombre d'articles achetés par le montant des sessions #########################

# Création DF des clients avec price.sum() et sessions.count()
Session_CA_nb_articles = transanalyse.groupby(['session_id',
                                    'client_id','sex','Age']).agg({'price':'sum', 
                                                                   'session_id': 'count'})
Session_CA_nb_articles = Session_CA_nb_articles.rename(columns={"price":"CA session", "session_id":"Nbr articles"})
Session_CA_nb_articles = Session_CA_nb_articles.reset_index()
Session_CA_nb_articles

sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 4))
plt.title("Nombres d'articles vendus en fonction du Chiffre d'affaires par session", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})

sns.scatterplot(data=Session_CA_nb_articles, x='CA session', y='Nbr articles', color='firebrick')
# Affichage
plt.show()
No description has been provided for this image
In [52]:
####################################### Analyse des sessions selon les tranches d'âges #################################

# Calul du nombre de sesions par âge
Ca_Freq = transanalyse.groupby(['Age'])['session_id'].count().reset_index()
Ca_Freq = Ca_Freq.sort_values(by='Age', ascending=True)
Ca_Freq = Ca_Freq.rename(columns={'session_id':"Nbr de sessions"})

# Calul du nombre de sesions par client
Ca_Freq1 = transanalyse.groupby(['client_id','Age'])['session_id'].count().reset_index()
Ca_Freq1 = Ca_Freq1.sort_values(by='Age', ascending=True)

# Calul du nombre moyen de sessions par âge
Ca_Freq2 = Ca_Freq1.groupby(['Age'])['session_id'].median().reset_index()
Ca_Freq2 = Ca_Freq2.rename(columns={'session_id':"Moyenne des sessions"})
Ca_Freq2= pd.merge(Ca_Freq2, Ca_Freq, on=['Age'], how ='left', indicator=True)
Ca_Freq2 = Ca_Freq2.drop('_merge', axis=1)
message = "Moyenne des sessions en fonction de l'âge"
Text_message(message)
display(Ca_Freq2.head(3))

# Calculer le CA global par tranche d'âge
Ca_3age = Tranches.groupby('Age-Range')['price'].sum().reset_index()
Ca_3age['price'] = round((Ca_3age['price'] / 1000),2)
Ca_3age['%'] = round(((Ca_3age['price'] *100) /  Ca_3age['price'].sum()),2)
Ca_3age = Ca_3age.sort_values(by=['Age-Range'], ascending=True)
message = "Distribution du Chiffre d'affaires par tranche d'âges"
Text_message(message)
display(Ca_3age)

# Calculer le CA par âge
Ca_age= transanalyse.groupby('Age')['price'].sum().reset_index()
Ca_age['%'] = round(((Ca_age['price'] *100) /  Ca_age['price'].sum()),2)
# Ajout de la colonne tranche d'âge
Ca_age['Age-Range'] = pd.cut(x=Ca_age['Age'], bins=[18,age1,age2,95])
# Jointure des 2 tables
Ca_age = pd.merge(Ca_3age, Ca_age, on=['Age-Range'], how ='right', indicator=True)
Ca_age = Ca_age.sort_values(by=['Age'], ascending=True)
Ca_age = Ca_age.rename(columns={'%_x':"Taux Tranche Ages", '%_y':"Taux Age",
                               'price_x':"CA Tranche Ages (en millier)", 'price_y':"CA par Age"})
Ca_age = Ca_age.drop('_merge', axis=1)
# Jointure avec la table 'Ca_Freq2' pour obtenir les sessions
Ca_age = pd.merge(Ca_age, Ca_Freq2, on=['Age'], how ='right', indicator=True)
Ca_age = Ca_age.drop('_merge', axis=1)
message = "Table détaillée - Regroupement des données"
Text_message(message)
display(Ca_age.tail(3))

# Dispatch entre les tranches d'âges
Ca_Freq_jeune = Ca_age.loc[(Ca_Freq['Age'] > age1) & (Ca_age['Age'] <= age2)]
Ca_Freq_jeune1 = Ca_age.loc[(Ca_Freq2['Age'] > age1) & (Ca_age['Age'] <= age2)]
Ca_Freq_Tjeune = Ca_age.loc[Ca_Freq['Age'] <= age1]
Ca_Freq_Tjeune1 = Ca_age.loc[Ca_Freq2['Age'] <= age1]
Ca_Freq_vieux = Ca_age.loc[Ca_Freq['Age'] > age2]
Ca_Freq_vieux1 = Ca_age.loc[Ca_Freq2['Age'] > age2]

# Création des DF utiles pour visualisation
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.subplot(1,2,1)

plt.title("Moyenne de sessions par âge sur 2 ans", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.regplot(data=Ca_Freq_vieux1, x='Age', y='Moyenne des sessions', marker='p', color='teal')
sns.regplot(data=Ca_Freq_jeune1, x='Age', y='Moyenne des sessions', marker='p', color='salmon')
sns.regplot(data=Ca_Freq_Tjeune1, x='Age', y='Moyenne des sessions', marker='p', color='seagreen')
plt.grid(color="darkgrey")
plt.subplot(1,2,2)
plt.title("Nombre de sessions par âge sur 2 ans", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.regplot(data=Ca_Freq_vieux, x='Age', y='Nbr de sessions', marker='p', color='teal')
sns.regplot(data=Ca_Freq_jeune, x='Age', y='Nbr de sessions', marker='p', color='salmon')
sns.regplot(data=Ca_Freq_Tjeune, x='Age', y='Nbr de sessions', marker='p', color='seagreen')
plt.grid(color="darkgrey")
plt.show()

# Créer la figure et les axes
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig, ax1 = plt.subplots(figsize=(12,3))
ax2 = ax1.twinx()
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})

plt.suptitle("Moyenne de sessions par âge sur 2 ans", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
plt.title("CA par tranche d'âges", y=1.05, 
          fontdict={'size': 15, 'style':'italic', 'color': 'teal'})

sns.regplot(data=Ca_Freq_vieux1, x='Age', y='Moyenne des sessions', marker='p', color='teal', ax=ax1)
sns.regplot(data=Ca_Freq_jeune1, x='Age', y='Moyenne des sessions', marker='p', color='salmon', 
            ax=ax1)
sns.regplot(data=Ca_Freq_Tjeune1, x='Age', y='Moyenne des sessions', marker='p', color='seagreen', 
            ax=ax1)
gfg = sns.lineplot(data=Ca_age, x='Age', y='CA Tranche Ages (en millier)', color="white", linewidth=4,
                   ax=ax2, alpha=0.8, label="Chiffre d'affaires")
gfg.set_ylim(1500, 5600)
plt.legend(facecolor='grey', labelcolor="white", fontsize='medium')
ax1.grid()
ax2.grid()
plt.show()
Moyenne des sessions en fonction de l'âge
Age Moyenne des sessions Nbr de sessions
0 19 31.0 15126
1 20 26.0 4365
2 21 29.0 4579
Distribution du Chiffre d'affaires par tranche d'âges
Age-Range price %
0 (18, 31] 3287.69 27.77
1 (31, 51] 5531.98 46.73
2 (51, 95] 3019.18 25.50
Table détaillée - Regroupement des données
Age-Range CA Tranche Ages (en millier) Taux Tranche Ages Age CA par Age Taux Age Moyenne des sessions Nbr de sessions
73 (51, 95] 3019.18 25.5 92 2815.45 0.02 36.5 170
74 (51, 95] 3019.18 25.5 93 4013.87 0.03 29.0 226
75 (51, 95] 3019.18 25.5 94 3221.85 0.03 49.0 201
No description has been provided for this image
No description has been provided for this image

Nous observons trois comportements bien distincts:

jusqu'à l'âge de 31 ans, la moyenne des sessions est relativement faible et tourne autours de 30. Cependant, le panier moyen est plus élévé et engendre un CA de plus de 3.2 millions d'euros

Entre 31 et 51 ans, la moyenne des sessions est très forte et engendre un Ca de plus de 5.5 millions d'euros

A partir de 51 ans, la moyenne des sessions baisse fortement et suit une légère décroissance

La tranche des 31-51 ans représente le coeur de cible de la librairie

La tranche des moins de 31 ans achètent des livres de qualité (cadeaux - collection...)

7.4 - Comparaison entre nombre de clients et Ca réalisé par tranche d'âges

In [53]:
# Calul du nombre de clients par âge
Nre_clients_age = transanalyse.groupby(['Age'])['client_id'].count().reset_index()
Nre_clients_age = Nre_clients_age.sort_values(by='Age', ascending=True)
Nre_clients_age = Nre_clients_age.rename(columns={'client_id':"Nbr de clients"})
# Ajout du Nbr de clients dans le DF principal
Ca_age = pd.merge(Ca_age, Nre_clients_age, on=['Age'], how ='right', indicator=True)
Ca_age = Ca_age.drop('_merge', axis=1)
#display(Ca_age.tail(3))
# Création du DF utile pour le graphique
Nre_clients_age = Ca_age.groupby(['Age-Range'])['Nbr de clients'].sum().reset_index()
Nre_clients_age['%'] = round(((Nre_clients_age['Nbr de clients'] * 100) / 
                              Nre_clients_age['Nbr de clients'].sum()),2)
#display(Nre_clients_age.tail(3))

# Diagrammes en secteurs
sns.set(rc={'axes.facecolor':'whitesmoke', 'figure.facecolor':'whitesmoke'})
plt.figure(figsize=(12,3)) 
colors = sns.color_palette('crest')
# Premier diagramme en secteurs
plt.subplot(1,2,1)
labels = ['[- de 31]', '[31-51]', '[51-95]']
plt.title("Répartition des clients par tranche d'âges", y=1.01, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'}) 
plt.pie(Nre_clients_age['%'], colors = colors, labels=labels,autopct = '%.2f%%',
       explode = [0.05,0.05,0.05], shadow = 'True',
       textprops = {'color': 'black','fontsize':10, 'weight': 'normal'},
       wedgeprops = {'linewidth': 2})
# Forcer le pie chart en cercle plutôt qu'en éllipse
plt.axis('equal') 
# Second diagramme en secteurs
plt.subplot(1,2,2)
colors = sns.color_palette('crest')
labels = ['[- de 31]', '[31-51]', '[51-95]']
plt.title("Répartition du CA par tranche d'âges", y=1.01, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'}) 
plt.pie(Ca_3age['price'], colors = colors, labels=labels,autopct = '%.2f%%',
       explode = [0.05,0.05,0.05], shadow = 'True',
       textprops = {'color': 'black','fontsize':10},
       wedgeprops = {'linewidth': 2})
# Forcer le pie chart en cercle plutôt qu'en éllipse
plt.axis('equal')
plt.show()
No description has been provided for this image

Observations / Points d'intérêts :

Bien que représentant seulement 11,75% des clients, les - de 31 ans engendrent 27,77% du chiffre d'affaires.

La tranche des 31-51 ans, comprenant plus de 60% de la clientèle, représente quasi 50% du chiffres d'affaires.

Que ce soit concernant la proportion clients ou du chiffre d'affaires, les 3/4 proviennent des moins de 51 ans.

7.5 - Répartition du Chiffre d'affaires

In [54]:
# Création du Df en vu de l'analyse
Rep_client = transanalyse.groupby('client_id')['price'].sum().reset_index()
Rep_client = Rep_client.rename(columns={"price":"CA/client"})
Rep_client["CA/client"] = round((Rep_client["CA/client"]),2)
Rep_client = Rep_client.sort_values(by='CA/client', ascending=False)

# Centrer l'affichage du DataFrame
message = "Top 4 clients"
Text_message(message)
centered_df = Rep_client.head(4).round(2).astype(str).style.set_table_attributes('style="margin-left: auto; margin-right: auto;"')
display(centered_df)

# Création du Df sans le top 4en vu de l'analyse
Rep_client_hors4 = Rep_client.loc[Rep_client['CA/client'] <= 6000]

# Sélection du sous-échantillon
dep = Rep_client_hors4['CA/client'].values
n = len(dep)
# Trie des individus dans l'ordre croissant des valeurs de la variable avec np.sort(dep)
# Calcule de la somme cumulée avec np.cumsum()
# Normaliser et diviser par  dep.sum() pour que le haut de la courbe soit à 1
lorenz = np.cumsum(np.sort(dep)) / dep.sum()
# La courbe de Lorenz commence à 0
lorenz = np.append([0],lorenz) 

# Surface sous la courbe de Lorenz. Le premier segment (lorenz[0]) est à moitié en dessous de 0, 
# on le coupe donc en 2, on fait de même pour le dernier segment lorenz[-1] qui est à moitié 
# au dessus de 1.
AUC = (lorenz.sum() -lorenz[-1]/2 -lorenz[0]/2)/n 
# surface entre la première bissectrice et le courbe de Lorenz
S = 0.5 - AUC 
gini1 = 2*S
# Création du graphique
fig = plt.figure(figsize=(12, 3))
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey', 'grid.color':'grey'})
plt.subplot(1,2,1)
#sns.set(rc={'axes.facecolor':'whitesmoke', 'figure.facecolor':'lightgrey', 'grid.color':'grey'})
plt.title("Mesure de concentration des clients hors 4 tops clients", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
# Il y a un segment de taille n pour chaque individu, plus 1 segment supplémentaire d'ordonnée 0. 
# Le premier segment commence à 0-1/n, et le dernier termine à 1+1/n.
xaxis = np.linspace(0-1/n,1+1/n,len(lorenz)) 
plt.plot(xaxis,lorenz,drawstyle='steps-post',linewidth=3,color='teal', linestyle='dashed',
        marker='o', markersize=0.1)
# Tracer la bisséctrice
plt.plot([0,1], [0,1],color='firebrick',linewidth=2) 
#tracer la Médiale Gini
# Affichage de l'indice de Gini
plt.text(0.0, 0.6, f'Indice de Gini: {gini1:.3f}', fontsize=10, bbox=dict(facecolor='white', 
        edgecolor='gray', boxstyle='round'))
# Tracé de l'indice de Gini
plt.axvline(x=gini1, color='white', linestyle='--', label='Indice de Gini')
plt.xlabel("% cumulé Clients",color='teal')
plt.ylabel("% cumulé CA",color='teal')
legend = plt.legend(['Courbe de Lorenz','Bisectrice','Indice de Gini'], title = "Légende", facecolor='white',
                    fontsize = 8, title_fontsize = 10)


plt.subplot(1,2,2)
# Sélection du sous-échantillon
dep = Rep_client['CA/client'].values
n = len(dep)
# Trie des individus dans l'ordre croissant des valeurs de la variable avec np.sort(dep)
# Calcule de la somme cumulée avec np.cumsum()
# Normaliser et diviser par  dep.sum() pour que le haut de la courbe soit à 1
lorenz = np.cumsum(np.sort(dep)) / dep.sum()
# La courbe de Lorenz commence à 0
lorenz = np.append([0],lorenz) 

# Surface sous la courbe de Lorenz. Le premier segment (lorenz[0]) est à moitié en dessous de 0, 
# on le coupe donc en 2, on fait de même pour le dernier segment lorenz[-1] qui est à moitié 
# au dessus de 1.
AUC = (lorenz.sum() -lorenz[-1]/2 -lorenz[0]/2)/n 
# surface entre la première bissectrice et le courbe de Lorenz
S = 0.5 - AUC 
gini1 = 2*S
# Création du graphique

plt.title("Mesure de concentration des clients", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
# Il y a un segment de taille n pour chaque individu, plus 1 segment supplémentaire d'ordonnée 0. 
# Le premier segment commence à 0-1/n, et le dernier termine à 1+1/n.
xaxis = np.linspace(0-1/n,1+1/n,len(lorenz)) 
plt.plot(xaxis,lorenz,drawstyle='steps-post',linewidth=3,color='teal', linestyle='dashed',
        marker='o', markersize=0.1)
# Tracer la bisséctrice
plt.plot([0,1], [0,1],color='firebrick',linewidth=2) 
#tracer la Médiale Gini
# Affichage de l'indice de Gini
plt.text(0.0, 0.6, f'Indice de Gini: {gini1:.3f}', fontsize=10, bbox=dict(facecolor='white', 
        edgecolor='gray', boxstyle='round'))
# Tracé de l'indice de Gini
plt.axvline(x=gini1, color='white', linestyle='--', label='Indice de Gini')
plt.xlabel("% cumulé Clients",color='teal')
plt.ylabel("% cumulé CA",color='teal')
legend = plt.legend(['Courbe de Lorenz','Bisectrice','Indice de Gini'], title = "Légende", facecolor='white',
                    fontsize = 8, title_fontsize = 10)
plt.show()
Top 4 clients
  client_id CA/client
677 c_1609 323678.54
4388 c_4958 288600.82
6337 c_6714 153417.5
2724 c_3454 113669.85
No description has been provided for this image

Observations / Points d'intérêts :

- La distance entre la courbe de Lorenz et la ligne d'égalité montre un degré d'inégalité significatif.

- L'indice de Gini de 0.4 ou 0.45 confirme l'inégalité entre les variables.

Tant la courbe de Lorenz que l'indice de Gini ne permettent pas de déterminer une équité ou une inégalité de niveau extrême.

7.6 - Analyse de la saisonnalité

In [55]:
# Création du DF pour la saisonnalité du Chiffre d'affaires
tableprix = Ca_categ_jour.groupby(['jour','month'])['CA/jour'].sum().reset_index()
tableprix["Day"] = tableprix["jour"].dt.strftime('%d %B')
tableprix["nom-jour"] = tableprix["jour"].dt.strftime('%A')
In [56]:
#################################### Choix du laps de temps pour l'analyse (N-1) ##########################################
Date_depart = "2021-03-01" # la date ne doit pas être inférieure à 2021-03-01
Date_fin = "2022-02-28" # la date ne doit pas dépasser le 2022-02-28
Interval_date = 15
###########################################################################################################################
dated0 = np.datetime64(Date_depart)
datef0 = np.datetime64(Date_fin)
dated1 = str(dated0 + np.timedelta64(365, 'D'))
datef1 = str(datef0 + np.timedelta64(365, 'D'))

# Préparation des Dataframe
tableprix0 = tableprix.loc[(tableprix['jour'] >= dated0) & (tableprix['jour'] <= datef0)]
tableprix1 = tableprix.loc[(tableprix['jour'] >= dated1) & (tableprix['jour'] <= datef1)]

# Création du graphique avec Seaborn
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(12,4)) 
plt.title("Saisonnalité du Chiffre d'affaires", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.lineplot(x='Day', y='CA/jour', data=tableprix0, color='teal', label='période N-1')
sns.lineplot(x='Day', y='CA/jour', data=tableprix1, color='salmon', label='période N')
# Configuration de l'intervalle entre les étiquettes de l'axe x
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=Interval_date))
plt.legend(loc='upper left', shadow=True, fontsize='small', facecolor='whitesmoke')
plt.grid(color='darkgrey')
# Inclinaison des étiquettes de l'axe x pour une meilleure lisibilité
plt.xticks(rotation=45)
# Afficher le graphique
plt.show()
No description has been provided for this image

Observations:

- Ce graphique ne donne aucune saisonnalité significative. Des analyses complémentaires s'imposent.

In [57]:
########################## Préparation des éléments pour les analyses de saisonnalité ################################
# Configuration régionale pour le français
locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8')
Saison_semaine = transanalyse.copy()
Saison_semaine["Jour semaine"] = Saison_semaine["date"].dt.strftime('%A')
Saison_semaine["Mois"] = Saison_semaine["date"].dt.strftime('%B')
Mois_accent = {'août': 'août', 'décembre': 'décembre', 'février': 'février'}
Saison_semaine['Mois'] = Saison_semaine['Mois'].replace(Mois_accent)
Saison_semaine["No semaine"] = Saison_semaine["date"].dt.strftime('%U')
Saison_semaine.loc[Saison_semaine['No semaine'] == '00', 'No semaine'] = '01'

Saison_semaineCA = Ca_categ_jour.copy()
Saison_semaineCA["Jour semaine"] = Saison_semaineCA["jour"].dt.strftime('%A')
Saison_semaineCA["Mois"] = Saison_semaineCA["jour"].dt.strftime('%B')
Saison_semaineCA["No semaine"] = Saison_semaineCA["jour"].dt.strftime('%U')
jours_mapping = {'août': 'août', 'décembre': 'décembre', 'février': 'février'}
Saison_semaineCA['Mois'] = Saison_semaineCA['Mois'].replace(jours_mapping)
Saison_semaineCA.loc[Saison_semaineCA['No semaine'] == '00', 'No semaine'] = '01'

############################################## SESSIONS ############################################
############################################### Table Nbr sessions Jours de la semaine
Saison_Jours = Saison_semaine.groupby(['Jour semaine'])['session_id'].count().reset_index()
# Définition de l'ordre des jours de la semaine
ordre_jours = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
# Triage des jours de la semaine selon l'ordre spécifié
Saison_Jours['Jour semaine'] = pd.Categorical(Saison_Jours['Jour semaine'], categories=ordre_jours, ordered=True)
############################################### Table Nbr sessions Mois de l'année
Saison_Mois = Saison_semaine.groupby(['Mois'])['session_id'].count().reset_index()
ordre_mois = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre',
             'décembre']
jours_mapping = {'août': 'août', 'décembre': 'décembre', 'février': 'février'}
Saison_Mois['Mois'] = Saison_Mois['Mois'].replace(jours_mapping)
Saison_Mois['Mois'] = pd.Categorical(Saison_Mois['Mois'], categories=ordre_mois, ordered=True)
# Ajout des sessions manquantes de la catégorie 1.0 sur octobre 2021 - Basé sur la différence avec octobre 2022
Cat1_octobre2022 = Saison_semaine.loc[(Saison_semaine['month'] == '2022-10') 
                                      & (Saison_semaine['categ'] == '1.0')].shape[0]
Cat1_octobre2021 = Saison_semaine.loc[(Saison_semaine['month'] == '2021-10') 
                                      & (Saison_semaine['categ'] == '1.0')].shape[0]
Manque_sessions_octobre2021 = Cat1_octobre2022 - Cat1_octobre2021
Saison_Mois.loc[10, 'session_id'] += Manque_sessions_octobre2021
################################################ Table Nbr sessions No de semaine
Saison_Semaines = Saison_semaine.groupby(['No semaine','year'])['session_id'].count().reset_index()
Saison_Semaines = Saison_Semaines.sort_values(by='No semaine', ascending=True).reset_index()
df = ['40', '41', '42', '43']
index = [79, 81 , 83, 85]
for valeur, i in zip(df,index):
    r= Saison_Semaines.loc[((Saison_Semaines['No semaine'] == valeur) & 
                                     (Saison_Semaines['year'] == 2021)),'session_id'].iloc[0]
    s= Saison_Semaines.loc[((Saison_Semaines['No semaine'] == valeur) & 
                                     (Saison_Semaines['year'] == 2022)),'session_id'].iloc[0]
    delta = s-r
    Saison_Semaines.loc[i, 'session_id'] += delta
Saison_Semaines = Saison_Semaines.groupby(['No semaine'])['session_id'].sum().reset_index()

############################################## CHIFFRE D'AFFAIRES ###################################
############################################### Table CA Jours de la semaine
Saison_JoursCA = Saison_semaineCA.groupby(['Jour semaine'])['CA/jour'].sum().reset_index()
# Définition de l'ordre des jours de la semaine
ordre_jours = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
# Triage des jours de la semaine selon l'ordre spécifié
Saison_JoursCA['Jour semaine'] = pd.Categorical(Saison_JoursCA['Jour semaine'], 
                                                categories=ordre_jours, ordered=True)
############################################### Table CA Mois de l'année
Saison_MoisCA = Saison_semaineCA.groupby(['Mois'])['CA/jour'].sum().reset_index()
ordre_mois = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 
              'septembre', 'octobre', 'novembre', 'décembre']
jours_mapping = {'août': 'août', 'décembre': 'décembre', 'février': 'février'}
Saison_MoisCA['Mois'] = Saison_MoisCA['Mois'].replace(jours_mapping)
Saison_MoisCA['Mois'] = pd.Categorical(Saison_MoisCA['Mois'], categories=ordre_mois, ordered=True)
################################################ Table CA No de semaine
Saison_SemainesCA = Saison_semaineCA.groupby(['No semaine'])['CA/jour'].sum().reset_index()
Saison_SemainesCA = Saison_SemainesCA.sort_values(by='No semaine', ascending=True)

#####################################  Graphiques JOUR de la semaine ###########################
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'}) 
fig = plt.figure(figsize=(12, 3))
plt.subplot(1,2,1)
# Graphique Sessions Jours de la semaine
plt.title("Saisonnalité des sessions - Jour de la semaine\nCumul N-1/N", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x='Jour semaine', y='session_id', data=Saison_Jours, palette='crest')
plt.xticks(rotation=45, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Nombre de sessions")
plt.xlabel('')
plt.ylim(95000, 98500)
# Graphique CA Jours de la semaine
plt.subplot(1,2,2)
plt.title("Saisonnalité du CA - Jour de la semaine\nCumul N-1/N", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x='Jour semaine', y='CA/jour', data=Saison_JoursCA, palette='flare')
plt.xticks(rotation=45, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Chiffre d'affaires")
plt.xlabel('')
plt.ylim(1700000, 1745000)
plt.show()

#####################################  Graphiques MOIS ###########################
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'}) 
fig = plt.figure(figsize=(12, 3))
# Graphique Sessions Mois de l'année
plt.subplot(1,2,1)
plt.title("Saisonnalité des sessions - Mois\nCumul N-1/N", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x='Mois', y='session_id', data=Saison_Mois, palette='crest')
# Inclinaison des étiquettes et taille de police
plt.xticks(rotation=45, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Nombre de sessions")
plt.xlabel('')
plt.ylim(48000, 62500)
# Graphique CA Mois de l'année
plt.subplot(1,2,2)
plt.title("Saisonnalité du CA - Mois\nCumul N-1/N", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x='Mois', y='CA/jour', data=Saison_MoisCA, palette='flare')
# Inclinaison des étiquettes et taille de police
plt.xticks(rotation=45, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.ylabel("Chiffre d'affaires")
plt.xlabel('')
plt.ylim(960000, 1050000)
plt.show()

#####################################  Graphiques de Saisonnalité SEMAINE ###########################
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(12,3))
plt.title("Saisonnalité des sessions - Semaines\nCumul N-1/N", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x='No semaine', y='session_id', data=Saison_Semaines, palette='crest')
plt.ylim(10500, 16000)
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.show()

#####################################  Graphiques de Chiffre d'affaires SEMAINE #####################
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
plt.figure(figsize=(12,3))
plt.title("Saisonnalité du CA - Semaines\nCumul N-1/N", y=1.05, 
               fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.barplot(x='No semaine', y='CA/jour', data=Saison_SemainesCA, palette='flare')
plt.ylim(210000, 280000)
plt.xticks(rotation=70, fontsize=10)
plt.yticks(rotation=0, fontsize=10)
plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Observations:

- L’activité est plus importante les Lundis et Mardis.

- Les Dimanches et Mercredis sont aussi notables.

- A la rentrée et sur la période de noël, le nombre de sessions est plus important.

- Le Chiffre d’affaires est plus important en Décembre et en Janvier

8- Tests de corrélations

8.1 - Corrélation entre le genre client et la catégorie de livre acheté

In [58]:
# Création du Dataframe utile à l'analyse
Y = "sex"
X = "categ"
Genre_cat = transanalyse[[X,Y]].pivot_table(index=X,columns=Y,aggfunc=len)
CAt_genre = transanalyse.groupby([X,Y])['price'].count().reset_index()
CAt_genre = CAt_genre.rename(columns={"price":"Nombre", "categ":"Catégorie","sex":"Genre"})

# Hypothèses du test
message = "Hypothèses du test"
Text_message(message)
message = "H0 (hypothèse nulle) : Il n'y a pas d'association entre les variables 'sex' et 'categ', elles sont indépendantes."
Text_message(message)
message = "H1 (hypothèse alternative) : Il y a une association entre les variables 'sex' et 'categ', elles ne sont pas indépendantes"
Text_message(message)
# Test CHI2
chi = chi2(Genre_cat)
message = "Résultat du Test Chi2 :"
Text_message(message)
message = "Statistique : {:.5f}".format(chi.statistic)
Text_message(message)
message = "pvalue     : {}".format(chi.pvalue)
Text_message(message)
print('Expected freq : ', chi.expected_freq)
print('------------')
alpha = 0.05  # Niveau de signification
if chi.pvalue < alpha:
    message = "Il y a une corrélation entre le genre et la catégorie de livres (hypothèse nulle rejetée)"
    Text_message(message)
else:
    message = "Il n'y a pas de corrélation entre le genre et la catégorie de livres (hypothèse nulle non rejetée)"
    Text_message(message)

    # Création du barplot en fonction de la catégorie et du genre    
# Création de la figure
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
#plt.subplot(1,2,1)
sns.set_theme(style="whitegrid")
sns.barplot(data=CAt_genre, x="Catégorie", y="Nombre", hue="Genre", palette="crest", 
            alpha=.8, ax=axes[0])

# Affichage des données observées sous forme de heatmap
sns.heatmap(Genre_cat, annot=True, cmap='crest', fmt='d', ax=axes[1])

# Titres des axes
axes[0].set_xlabel('Catégories')
axes[0].set_ylabel('Nbr de sessions')
axes[1].set_xlabel('Genres')
axes[1].set_ylabel('')
# Titre du graphe
axes[0].set_title("Répartition par catégorie et par genre", y=1.01, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
axes[1].set_title("Chi-Square Test", y=1.01, 
          fontdict={'size': 14, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
# Affichage du graphe
plt.tight_layout()
plt.show()
Hypothèses du test
H0 (hypothèse nulle) : Il n'y a pas d'association entre les variables 'sex' et 'categ', elles sont
indépendantes.
H1 (hypothèse alternative) : Il y a une association entre les variables 'sex' et 'categ', elles ne
sont pas indépendantes
Résultat du Test Chi2 :
Statistique : 143.28034
pvalue : 7.710273445342749e-32
Expected freq :  [[206838.59230662 208392.40769338]
 [113020.98120574 113870.01879426]
 [ 18123.42648765  18259.57351235]]
------------
Il y a une corrélation entre le genre et la catégorie de livres (hypothèse nulle rejetée)
No description has been provided for this image

Observations :

Avec le test du Chi2, nous observons une corrélation entre le genre des clients et la catégorie de livre achetée

les données fournissent des preuves solides pour rejeter l'hypothèse nulle. Il y a une association significative entre les variables étudiées, les fréquences observées diffèrent considérablement des fréquences attendues selon l'hypothèse nulle.

Les catégories 0.0 et 2.0 sont légèrement plus prisées par les hommes.

La catégorie 1.0 est légèrement plus prisée par les femmes.

8.2 - Corrélation entre l'âge des clients et le montant total des achats

In [59]:
# Création du Dataframe utile à l'analyse
Corr_age_somme = transanalyse.groupby('Age')['price'].sum().reset_index()
# Vérification de distribution normale
# Échantillon de données
x = Corr_age_somme['Age']
y = Corr_age_somme['price']

################################ Test de Shapiro-Wilk ########################
message = 'Test de Shapiro-Wilk'
Text_message(message)
statistic, p_value = st.shapiro(x)
statistic1, p_value1 = st.shapiro(y)
message = "Statistique de test : {:.5f}  et P_value : {}".format(statistic,p_value)
Text_message(message)
# Interprétation des résultats
if p_value > 0.05:
    message = "L'échantillon 'Age' suit une distribution normale."
else:
    message = "L'échantillon 'Age' ne suit pas une distribution normale."
Text_message(message)
message = "Statistique de test : {:.5f}  et P_value : {}".format(statistic1,p_value1)
Text_message(message)
if p_value1 > 0.05:
    message = "L'échantillon 'price' suit une distribution normale."
else:
    message = "L'échantillon 'price' ne suit pas une distribution normale."
Text_message(message) 

# Calculer la corrélation de rang de Spearman
# Création du Dataframe utile à l'analyse
Y = "sex"
X = "categ"
Genre_cat = transanalyse[[X,Y]].pivot_table(index=X,columns=Y,aggfunc=len)
CAt_genre = transanalyse.groupby([X,Y])['price'].count().reset_index()
CAt_genre = CAt_genre.rename(columns={"price":"Nombre", "categ":"Catégorie","sex":"Genre"})

# Hypothèses du test
message = "#######################################################"
Text_message(message)
message = "Hypothèses du test de Spearman"
Text_message(message)
message = "H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'price'."
Text_message(message)
message = "H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'price'."
Text_message(message)

correlation, p_value = spearmanr(x, y)
alpha = 0.05  # Niveau de signification
message = "**************SUR L'ENSEMBLE DES DONNEES******************"
Text_message(message)

if p_value < alpha:
    message = "Il y a une corrélation négative significative entre l'âge et le montant des achats, on rejette H0"
    Text_message(message)
    message = "Corrélation de rang de Spearman :  {:.5f}".format(correlation)
    Text_message(message)
    message = "p_value Spearman :  {}".format(p_value)
    Text_message(message)
else:
    message = "Il n'y a pas de corrélation significative entre l'âge et le montant des achats"
    Text_message(message)
    message = "Corrélation de rang de Spearman :  {:.5f}".format(correlation)
    Text_message(message)
    message = "p_value Spearman :  {}".format(p_value)
    Text_message(message)
    
Corr_age_somme50 = Corr_age_somme.loc[Corr_age_somme['Age'] >50]
Corr_age_somme20 = Corr_age_somme.loc[Corr_age_somme['Age'] <=50]
######################### test de Spearman sur les moins de 50 ans ################
x1 = Corr_age_somme20['Age']
y1 = Corr_age_somme20['price']
# Calculer la corrélation de rang de Spearman
correlation, p_value = spearmanr(x1, y1)
alpha = 0.05  # Niveau de signification
print(" ")
message = "**************SUR LES MOINS DE 50 ANS**************"
Text_message(message)
if p_value < alpha:
    message = "Il y a une corrélation faible et incertaine entre l'âge et le montant des achats, on rejette H0"
    Text_message(message)
    message = "Corrélation de rang de Spearman :  {:.5f}".format(correlation)
    Text_message(message)
    message = "p_value Spearman :  {:.5f}  / NB: il n'y a plus de corrélation au niveau de signification 1%".format(p_value)
    Text_message(message)
else:
    message = "Il n'y a pas de corrélation significative entre l'âge et le montant des achats"
    Text_message(message)
    message = "Corrélation de rang de Spearman :  {:.5f}".format(correlation)
    Text_message(message)
    message = "p_value Spearman :  {:.5f}".format(p_value)
    Text_message(message)
    
######################### test de Spearman sur les plus de 50 ans ################
x2 = Corr_age_somme50['Age']
y2 = Corr_age_somme50['price']
# Calculer la corrélation de rang de Spearman
correlation, p_value = spearmanr(x2, y2)
alpha = 0.05  # Niveau de signification
print(" ")
message = "**************SUR LES PLUS DE 50 ANS**************"
Text_message(message)
if p_value < alpha:
    message = "Il y a une corrélation négative forte et significative entre l'âge et le montant des achats, on rejette H0"
    Text_message(message)
    message = "Corrélation de rang de Spearman :  {:.5f}".format(correlation)
    Text_message(message)
    message = "p_value Spearman :  {}".format(p_value)
    Text_message(message)
else:
    print("Il n'y a pas de corrélation significative entre l'âge et le montant des achats") 

# Création des DF utiles pour visualisation
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.subplot(1,2,1)
plt.title("Corrélation suivant l'âge", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.regplot(data=Corr_age_somme50, x='Age', y='price', marker='p', color='teal')
sns.regplot(data=Corr_age_somme20, x='Age', y='price', marker='p', color='salmon')
plt.ylabel("Montant des achats")
plt.grid(color='darkgrey')    
# Calcul de la régression linéaire
coefficients = np.polyfit(x, y, 1)
slope = coefficients[0]
intercept = coefficients[1]
regression_line = slope * x + intercept
# Création du diagramme de dispersion et de la régression linéaire
plt.subplot(1,2,2)
plt.title("Corrélation sur l'ensemble des clients", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
plt.scatter(x, y, c='b', label='Montant Achats')
plt.plot(x, regression_line, c='r', label='Régression linéaire')
plt.grid(color='darkgrey')  
plt.xlabel("Age")
plt.legend(facecolor ='whitesmoke')
plt.show()     
Test de Shapiro-Wilk
Statistique de test : 0.95492 et P_value : 0.008753503672778606
L'échantillon 'Age' ne suit pas une distribution normale.
Statistique de test : 0.88810 et P_value : 6.431340807466768e-06
L'échantillon 'price' ne suit pas une distribution normale.
#######################################################
Hypothèses du test de Spearman
H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'price'.
H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'price'.
**************SUR L'ENSEMBLE DES DONNEES******************
Il y a une corrélation négative significative entre l'âge et le montant des achats, on rejette H0
Corrélation de rang de Spearman : -0.85761
p_value Spearman : 4.57972879340901e-23
 
**************SUR LES MOINS DE 50 ANS**************
Il y a une corrélation faible et incertaine entre l'âge et le montant des achats, on rejette H0
Corrélation de rang de Spearman : 0.41092
p_value Spearman : 0.01947 / NB: il n'y a plus de corrélation au niveau de signification 1%
 
**************SUR LES PLUS DE 50 ANS**************
Il y a une corrélation négative forte et significative entre l'âge et le montant des achats, on
rejette H0
Corrélation de rang de Spearman : -0.96688
p_value Spearman : 1.5610778628201873e-26
No description has been provided for this image

Test de Spearman :

Pour les moins de 50 ans, la corrélation est faible et incertaine entre l'âge des clients et le montant des achats

Pour les plus de 50 ans, la corrélation devient forte entre l'âge des clients et le montant des achats

In [60]:
############################### TEST ANOVA ########################
# Création du df pour effectuer le test
transanalyse1 = transanalyse[['price','Age-Range']]
transanalyse1 = transanalyse1.pivot_table(index='price', 
                                          columns="Age-Range",aggfunc=len)
# Remplacer les nan par des 0
transanalyse1 = transanalyse1.fillna(0)
transanalyse1.columns = ['18-25', '25-35', '35-45', '45-55', '55-65','65-75','75-85','+85']
transanalyse1 = transanalyse1.reset_index()
# Remodeler le Dataframe adapté au package StatsModels
transanalyse1_melt = transanalyse1.melt( id_vars=['price'], 
                                        value_vars=['18-25', '25-35', '35-45', '45-55', 
                                                    '55-65','65-75','75-85','+85'])
#display(transanalyse1.head(3))
#display(transanalyse1_melt.head(3))

############################ Test ANOVA (analyse de variance) ###########################
# Hypothèses du test
message = "Hypothèses du test ANOVA"
Text_message(message)
message = "H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence statistiquement significative entre les groupes."
Text_message(message)
message = "H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence statistiquement significative entre au moins deux groupes."
Text_message(message)
message = "#######################################################"
Text_message(message)
# stats f_oneway fonctions prend les groupes comme entrée et renvoie la valeur ANOVA F et p-value
fvalue, pvalue = st.f_oneway(transanalyse1['18-25'], transanalyse1['25-35'], transanalyse1['35-45'], 
                             transanalyse1['45-55'], transanalyse1['55-65'], transanalyse1['65-75'], 
                             transanalyse1['75-85'], transanalyse1['+85'])
message = "Test Anova avec f_oneway"
Text_message(message)
message = "Statistique de test: {:.5f}".format(fvalue)
Text_message(message)
message = "p_value {}".format(pvalue)
Text_message(message)
print('--------------------------------------------------------')
# Adaptation du nom des colonnes en vu du test Anova
trans_melt = transanalyse1_melt
trans_melt.columns = ['index', "tranchages", "valeur"]
# Modèle des moindres carrés ordinaires (MCO) (OLS en anglais) 
model = ols('valeur ~ C(tranchages)', data=trans_melt).fit()
anova_table = sm.stats.anova_lm(model)
# sortie (ANOVA F et valeur p)
message = 'Test Anova suivant le modèle des moindres carrés ordinaires'
Text_message(message)
display(anova_table)
print('--------------------------------------------------------')
########################### AUTRE POSIBILITE ###########################################
# Table ANOVA avec bioinfokit (elle utilise un script wrapper pour anova_lm)
from bioinfokit.analys import stat
res = stat()
res.anova_stat(df=trans_melt, res_var='valeur', anova_model='valeur ~ C(tranchages)')
message = 'Test Anova avec bioinfokit'
Text_message(message)
display(res.anova_summary)
########################### résultat corrélation ###########################################
alpha = 0.05  # Niveau de signification
if pvalue < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le montant des achats"
    Text_message(message)
else:
    message = "on ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives entre l'âge et le montant des achats"
    Text_message(message)

########################### Graphique ###########################################
# Renommer les colonnes
transanalyse1_melt.columns = ['prix', "Tranche d'ages", "Nbr commandes par prix du livre"]
#display(transanalyse1_melt.head(5))

# Générez un boxplot pour voir la distribution des données par tranche d'ages
medianprops = dict(linestyle='-', linewidth=2, color='coral')
meanprops = dict(marker='p',markerfacecolor='firebrick', markeredgecolor='black', markersize=8)
boxprops = dict(linestyle='-', linewidth=0)
# Nous pouvons facilement détecter les différences entre les différentes tranches d'ages
sns.set(rc={'axes.facecolor':'white', 'figure.facecolor':'lightgrey'})
plt.subplots(figsize=(12,3))
plt.title("Distribution du montant des achats par tranche d'âges", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
ax = sns.boxplot(x="Tranche d'ages", y="Nbr commandes par prix du livre", data=transanalyse1_melt,
                 palette='crest', showmeans=True, 
            showfliers=False, notch = True, meanprops=meanprops, medianprops=medianprops, 
                 boxprops = boxprops)
plt.grid(color='darkgrey')
plt.show()
Hypothèses du test ANOVA
H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence
statistiquement significative entre les groupes.
H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence
statistiquement significative entre au moins deux groupes.
#######################################################
Test Anova avec f_oneway
Statistique de test: 44.25842
p_value 3.5163014462702307e-62
--------------------------------------------------------
Test Anova suivant le modèle des moindres carrés ordinaires
df sum_sq mean_sq F PR(>F)
C(tranchages) 7.0 3.013551e+07 4.305074e+06 44.258419 3.516301e-62
Residual 11528.0 1.121343e+09 9.727129e+04 NaN NaN
--------------------------------------------------------
Test Anova avec bioinfokit
df sum_sq mean_sq F PR(>F)
C(tranchages) 7.0 3.013551e+07 4.305074e+06 44.258419 3.516301e-62
Residual 11528.0 1.121343e+09 9.727129e+04 NaN NaN
On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le montant des
achats
No description has been provided for this image

Test ANOVA: On rejette l'hypothèse nulle, il existe une différence significative entre les échantillons

8.3 - Corrélation entre l'âge des clients et la fréquence d'achat

In [61]:
# Création du Df par âge utile à l'analyse 
Freq_achat_client = transanalyse.groupby(['Age','year','month', 'client_id'])[[
    'session_id']].nunique()
Freq_achat_client = Freq_achat_client.groupby(['Age']).count().reset_index()

# Données d'âge des clients (variable x) et fréquence d'achat (variable y)
x = Freq_achat_client['Age']
y = Freq_achat_client['session_id']

# Hypothèses du test
message = "Hypothèses du test de Spearman"
Text_message(message)
message = "H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'fréquence d'achat'."
Text_message(message)
message = "H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'fréquence d'achat'."
Text_message(message)
message = "#######################################################"
Text_message(message)
# Calcul de la corrélation et du p-value
correlation, p_value = st.spearmanr(x, y)
correl = correlation **2
if p_value < alpha:
    message = "Il y a une corrélation négative significative entre l'âge et la fréquence d'achat, on rejette H0"
    Text_message(message)
    message = "Corrélation de Spearman au carré (r2) :  {:.5f}".format(correl)  
    Text_message(message)
else:
    print("Il n'y a pas de corrélation entre l'âge et la fréquence d'achat")
# Affichage des résultats
message = "Corrélation de Spearman :  {:.5f}".format(correlation)
Text_message(message)
message = "p_value Spearman :  {}".format(p_value)
Text_message(message)
# Préparation graphique
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
# Calcul de la régression linéaire
coefficients = np.polyfit(x, y, 1)
slope = coefficients[0]
intercept = coefficients[1]
regression_line = slope * x + intercept

# Création du diagramme de dispersion et de la régression linéaire
sns.scatterplot(x=x, y=y, color='teal', label="Fréquence d'achat")
plt.plot(x, regression_line, c='r', label='Régression linéaire')
plt.title("Corrélation sur l'ensemble des clients", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
plt.xlabel("Age")
plt.ylabel("")
plt.legend(facecolor='whitesmoke')
plt.show()
Hypothèses du test de Spearman
H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'fréquence
d'achat'.
H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'fréquence
d'achat'.
#######################################################
Il y a une corrélation négative significative entre l'âge et la fréquence d'achat, on rejette H0
Corrélation de Spearman au carré (r2) : 0.53682
Corrélation de Spearman : -0.73268
p_value Spearman : 5.35806184758559e-14
No description has been provided for this image

Test de Spearman :

Le r2 indique une relation monotone incertaine dans laquelle 53% de la variance des données est bien modélisée

La corrélation de Spearman de -0,73 indique une forte corrélation négative monotone entre les variables

La p-value de 5,39e-14 indique que la probabilité d'observer une corrélation de Spearman aussi extrême que -0,73, simplement par pur hasard, est extrêmement faible

In [62]:
# Création du Df par tranches d'âges utile à l'analyse 
Corr_age_freq = transanalyse.groupby(['Age-Range','Age'])['session_id'].count().reset_index()
Corr_age_freq["fréquence"] = round((Corr_age_freq["session_id"] / len(transanalyse)),3)
Corr_age_freq["fréquence cum"] = Corr_age_freq["fréquence"].cumsum() # cumsum calcule la somme cumulée
# Supprimer les lignes où il n'y a pas de session 
Corr_age_freq.drop( Corr_age_freq[ Corr_age_freq['session_id'] == 0 ].index, inplace=True)
#display(Corr_age_freq.head(3))

# Création du df pour effectuer le test
Corr_age_freq1 = Corr_age_freq[['session_id','Age-Range','Age']]
Corr_age_freq1 = Corr_age_freq1.pivot_table(index='Age', values='session_id', 
                                          columns="Age-Range")
# Renommer les colonnes
Corr_age_freq1.columns = ['18-25', '25-35', '35-45', '45-55', '55-65','65-75','75-85','+85']
# Remplacer les nan par des 0
Corr_age_freq1 = Corr_age_freq1.fillna(0)
Corr_age_freq1 = Corr_age_freq1.reset_index()
# Hypothèses du test
message = "Hypothèses du test ANOVA"
Text_message(message)
message = "H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence statistiquement significative entre les groupes."
Text_message(message)
message = "H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence statistiquement significative entre au moins deux groupes."
Text_message(message)
message = "#######################################################"
Text_message(message)
message = "Test Anova avec f_oneway"
Text_message(message)
display(Corr_age_freq1.head(3))

######################################################################################################""
# stats f_oneway fonctions prend les groupes comme entrée et renvoie la valeur ANOVA F et p-value
fvalue, pvalue = st.f_oneway(Corr_age_freq1['18-25'], Corr_age_freq1['25-35'], Corr_age_freq1['35-45'], 
                             Corr_age_freq1['45-55'], Corr_age_freq1['55-65'], Corr_age_freq1['65-75'], 
                             Corr_age_freq1['75-85'], Corr_age_freq1['+85'])
message = "Statistique de test: {:.5f}".format(fvalue)
Text_message(message)
message = "p_value {}".format(pvalue)
Text_message(message)
alpha = 0.05  # Niveau de signification
if pvalue < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et la fréquence des achats"
else:
    message = "on ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives  entre l'âge et la fréquence des achats"
Text_message(message)

######################################################################################################""
# Remodeler le Dataframe adapté au package StatsModels
Corr_age_melt = Corr_age_freq1.melt( id_vars=['Age'], 
                                        value_vars=['18-25', '25-35', '35-45', '45-55', 
                                                    '55-65','65-75','75-85','+85'])
Corr_age_melt.columns = ['Age', "tranchages", "valeur"]
# Corr_age_melt = Corr_age_melt.fillna(0)
Corr_age_melt.drop( Corr_age_melt[ Corr_age_melt['valeur'] == 0 ].index, inplace=True)

# Adaptation du nom des colonnes en vu du test Anova
#Corr_age_melt.columns = ['Age', "tranchages", "valeur"]
# Modèle des moindres carrés ordinaires (MCO) (OLS en anglais) 
model = ols('valeur ~ C(tranchages)', data=Corr_age_melt).fit()
anova_table = sm.stats.anova_lm(model)
# sortie (ANOVA F et valeur p)
message = 'Test Anova suivant le modèle des moindres carrés ordinaires'
Text_message(message)
# Renommer les colonnes
Corr_age_melt.columns = ['Age', "Tranche d'ages", "Nbr de sessions"]
display(Corr_age_melt.head(3))
display(anova_table)
alpha = 0.05  # Niveau de signification
if pvalue < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et la fréquence des achats"
else:
    message = "on ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives  entre l'âge et la fréquence des achats"
Text_message(message)   
########################### Graphique ###########################################
# Générez un boxplot pour voir la distribution des données par tranche d'ages
# Préparation des attributs du boxplot
medianprops = dict(linestyle='-', linewidth=2, color='coral')
meanprops = dict(marker='p',markerfacecolor='firebrick', markeredgecolor='black', markersize=8)
boxprops = dict(linestyle='-', linewidth=0)
# Nous pouvons facilement détecter les différences entre les différentes tranches d'ages
sns.set(rc={'axes.facecolor':'white', 'figure.facecolor':'lightgrey'})
plt.subplots(figsize=(12,3))
plt.title("Distribution de la Fréquence d'achat par tranche d'âges", y=1.05, 
          fontdict={'size': 12, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.boxplot(x="Tranche d'ages", y="Nbr de sessions", data=Corr_age_melt, palette='crest', showmeans=True, 
            showfliers=False, notch = False, meanprops=meanprops, medianprops=medianprops, boxprops = boxprops)
plt.grid(color='darkgrey')
plt.ylabel("Fréquence d'achat")
plt.show()
Hypothèses du test ANOVA
H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence
statistiquement significative entre les groupes.
H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence
statistiquement significative entre au moins deux groupes.
#######################################################
Test Anova avec f_oneway
Age 18-25 25-35 35-45 45-55 55-65 65-75 75-85 +85
0 19 15126.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 20 4365.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 21 4579.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Statistique de test: 4.72043
p_value 3.575369782606219e-05
On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et la fréquence des
achats
Test Anova suivant le modèle des moindres carrés ordinaires
Age Tranche d'ages Nbr de sessions
0 19 18-25 15126.0
1 20 18-25 4365.0
2 21 18-25 4579.0
df sum_sq mean_sq F PR(>F)
C(tranchages) 7.0 4.140623e+09 5.915175e+08 26.578797 3.737032e-17
Residual 68.0 1.513356e+09 2.225524e+07 NaN NaN
On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et la fréquence des
achats
No description has been provided for this image

Test ANOVA: On rejete l'hypothèse nulle, il existe une différence significative entre les moyennes des groupes

8.4 - Corrélation entre l'âge des clients et la taille du panier moyen

In [63]:
# Création du Df par âge utile à l'analyse 
Age_panier = transanalyse.groupby(['Age-Range','Age'])['price'].mean().reset_index()
Age_panier = Age_panier.rename(columns={"price":"Panier moyen"})
Age_panier["Panier moyen"] = round((Age_panier["Panier moyen"]),2)

# Supprimer les lignes où le panier moyen = 0
Age_panier['Panier moyen'] = Age_panier['Panier moyen'].fillna(0)
Age_panier = Age_panier.drop(Age_panier[Age_panier['Panier moyen'] == 0].index)
display(Age_panier.tail(3))
panier_moyen_zero = Age_panier.loc[Age_panier['Panier moyen'] == 0]
print(panier_moyen_zero)
# Vérification de distribution normale
# Échantillon de données
x = Age_panier['Age']
y = Age_panier['Panier moyen']
# Test de Shapiro-Wilk
message = 'Test de Shapiro-Wilk'
Text_message(message)
statistic, p_value = st.shapiro(x)
statistic1, p_value1 = st.shapiro(y)
message = "Statistique de test : {:.5f}  et P_value : {}".format(statistic,p_value)
Text_message(message)
# Interprétation des résultats
if p_value > 0.05:
    message = "L'échantillon 'Age' suit une distribution normale."
else:
    message = "L'échantillon 'Age' ne suit pas une distribution normale."
Text_message(message)
print("--------------------------------") 
message = "Statistique de test : {:.5f}  et P_value : {}".format(statistic1,p_value1)
Text_message(message)
if p_value1 > 0.05:
    message = "L'échantillon 'Panier moyen' suit une distribution normale."
else:
    message = "L'échantillon 'Panier moyen' ne suit pas une distribution normale."
Text_message(message)
message = "#######################################################"
Text_message(message)

# Données d'âge des clients (variable x) et du panier moyen (variable y)
#x = Age_panier['Age']
#y = Age_panier['Panier moyen']
# Hypothèses du test
message = "Hypothèses du test de Spearman"
Text_message(message)
message = "H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'panier moyen'."
Text_message(message)
message = "H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'panier moyen."
Text_message(message)
message = "#######################################################"
Text_message(message)
# Calcul de la corrélation et du p-value
# Données d'âge des clients (variable x) et du panier moyen (variable y)
x = Age_panier['Age']
y = Age_panier['Panier moyen']
message = 'Test de Spearman'
Text_message(message)
correlation, p_value = st.spearmanr(x, y)
correl = correlation **2
if p_value < alpha:
    message = "Il y a une corrélation entre l'âge et le panier moyen, on rejette H0"
    Text_message(message)
    message = "Corrélation de Spearman au carré (r2) : {:.4f}".format(correl)
    Text_message(message)
else:
    message = "Il n'y a pas de corrélation entre l'âge et le panier moyen, H0 non rejetée"
    Text_message(message)
    message = "Corrélation de Spearman au carré (r2) : {:.4f}".format(correl)
    Text_message(message)

sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))

# Calcul de la régression linéaire avec scipy.stats.linregress
slope, intercept, _, _, _ = linregress(x, y)
regression_line = slope * x + intercept

# Création du diagramme de dispersion et de la régression linéaire
sns.scatterplot(x=x, y=y, color='teal', label='Données observées')
plt.plot(x, regression_line, c='r', label='Régression linéaire')
plt.title("Test de corrélation de Spearman")
plt.xlabel("Age")
plt.ylabel("Panier moyen")
plt.legend(facecolor='whitesmoke')
plt.show()
Age-Range Age Panier moyen
605 (85, 95] 92 16.56
606 (85, 95] 93 17.76
607 (85, 95] 94 16.03
Empty DataFrame
Columns: [Age-Range, Age, Panier moyen]
Index: []
Test de Shapiro-Wilk
Statistique de test : 0.95492 et P_value : 0.008753503672778606
L'échantillon 'Age' ne suit pas une distribution normale.
--------------------------------
Statistique de test : 0.60946 et P_value : 6.553231807031323e-13
L'échantillon 'Panier moyen' ne suit pas une distribution normale.
#######################################################
Hypothèses du test de Spearman
H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'panier
moyen'.
H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'panier
moyen.
#######################################################
Test de Spearman
Il n'y a pas de corrélation entre l'âge et le panier moyen, H0 non rejetée
Corrélation de Spearman au carré (r2) : 0.0071
No description has been provided for this image

Test de Spearman :

La corrélation de Spearman de -0,08 indique une corrélation inexistante négative monotone entre les variables

La p-value de 0.47 indique une probabilité non exploitable

On conclue qu'il n'existe aucune corrélation entre les variables

In [64]:
# Création du df pour effectuer le test
Age_panier1 = Age_panier[['Panier moyen','Age-Range','Age']]
Age_panier1 = Age_panier1.pivot_table(index='Age', values='Panier moyen', 
                                          columns="Age-Range")

# Renommer les colonnes
Age_panier1.columns = ['18-25', '25-35', '35-45', '45-55', '55-65','65-75','75-85','+85']
# Remplacer les nan par des 0
Age_panier1 = Age_panier1.fillna(0)
Age_panier1 = Age_panier1.reset_index()

# Remodeler le Dataframe adapté au package StatsModels
Age_panier_melt = Age_panier1.melt( id_vars=['Age'], 
                                        value_vars=['18-25', '25-35', '35-45', '45-55', 
                                                    '55-65','65-75','75-85','+85'])
# Adaptation des colonnes pour l'Anova
Age_panier_melt.columns = ['Age', "tranchages", "valeur"]
Age_panier_melt.drop( Age_panier_melt[ Age_panier_melt['valeur'] == 0 ].index, inplace=True)

# Hypothèses du test
message = "Hypothèses du test ANOVA"
Text_message(message)
message = "H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence statistiquement significative entre les groupes."
Text_message(message)
message = "H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence statistiquement significative entre au moins deux groupes."
Text_message(message)
message = "#######################################################"
Text_message(message)
# Modèle des moindres carrés ordinaires (MCO) (OLS en anglais) 
message = "Test Anova suivant le modèle des moindres carrés ordinaires - 8 tranches d'âges"
Text_message(message)
model = ols('valeur ~ C(tranchages)', data=Age_panier_melt).fit()
anova_table = sm.stats.anova_lm(model)
# Renommer les colonnes
Age_panier_melt.columns = ['Age', "Tranche d'ages", "Panier moyen"]
display(Age_panier_melt.head(3))
# Affichage résultats (ANOVA F et valeur p)
display(anova_table)
alpha = 0.05
if pvalue < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen"
else:
    message = "On ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives entre l'âge et le panier moyen"
    
Text_message(message)
# stats f_oneway fonctions prend les groupes comme entrée et renvoie la valeur ANOVA F et p-value
fvalue, pvalue = st.f_oneway(Age_panier1['18-25'], Age_panier1['25-35'], Age_panier1['35-45'], 
                             Age_panier1['45-55'], Age_panier1['55-65'], Age_panier1['65-75'], 
                             Age_panier1['75-85'], Age_panier1['+85'])
message = "Test Anova avec f_oneway sur les 8 tranches d'âges"
Text_message(message)
display(Age_panier1.head(3))
message = "Statistique de test = {:.5f}".format(fvalue)
Text_message(message) 
message = "p-valeur = {:.5f}".format(pvalue)
Text_message(message) 
alpha = 0.05  # Niveau de signification
if pvalue < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen"
else:
    message = "On ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives entre l'âge et le panier moyen"
    
Text_message(message)
########################### Graphique ###########################################
fig, ax1 = plt.subplots(figsize=(12,3))
ax2 = ax1.twiny()
sns.boxplot(x="Age-Range", y="Panier moyen", data=Age_panier, palette='crest',
            showfliers=True, ax=ax1)
sns.scatterplot(x="Age", y="Panier moyen", data=Age_panier, color='red', alpha=0.5, ax=ax2)
plt.show()

Age_panier35 = Age_panier_melt.loc[Age_panier['Age'] <= 35]
Age_panier90 = Age_panier_melt.loc[Age_panier['Age'] > 35]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 3))
ax1twin = ax1.twiny()
sns.boxplot(x="Tranche d'ages", y="Panier moyen", data=Age_panier35, palette='crest',
            showfliers=False, ax=ax1)
sns.scatterplot(x="Age", y="Panier moyen", data=Age_panier35, color='red', alpha=0.5, ax=ax1twin)
ax2twin = ax2.twiny()
sns.boxplot(x="Tranche d'ages", y="Panier moyen", data=Age_panier90, palette='crest',
            showfliers=False, ax=ax2)
sns.scatterplot(x="Age", y="Panier moyen", data=Age_panier90, color='red', alpha=0.5, ax=ax2twin)
plt.show()

########################### Même démarche avec 3 tranches d'âges #####################################                
# Création du Df par âge utile à l'analyse 
TAge_panier = Tranches.groupby(['Age-Range','Age'])['price'].mean().reset_index()
TAge_panier = TAge_panier.rename(columns={"price":"Panier moyen"})
TAge_panier["Panier moyen"] = round((TAge_panier["Panier moyen"]),2)
TAge_panier1 = TAge_panier.pivot_table(index='Age', values='Panier moyen', 
                                          columns="Age-Range")
TAge_panier1.columns = ['18-31','31-51','+51']
TAge_panier1 = TAge_panier1.fillna(0)
TAge_panier1 = TAge_panier1.reset_index()
TAge_panier_melt = TAge_panier1.melt( id_vars=['Age'], value_vars=['18-31','31-51','+51'])
TAge_panier_melt.columns = ['Age', "Age-Range", "Panier moyen"]
TAge_panier_melt.drop( TAge_panier_melt[ TAge_panier_melt['Panier moyen'] == 0 ].index, inplace=True)

# stats f_oneway fonctions prend les groupes comme entrée et renvoie la valeur ANOVA F et p-value
message = "Test Anova avec f_oneway sur les 3 tranches d'âges"
Text_message(message)
display(TAge_panier_melt.tail(3))
fvalue, pvalue = st.f_oneway(TAge_panier1['18-31'], TAge_panier1['31-51'], TAge_panier1['+51'])
message = "Statistique de test = {:.5f}".format(fvalue)
Text_message(message) 
message = "p-valeur = {}".format(pvalue)
Text_message(message)
alpha = 0.05  # Niveau de signification
if pvalue < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen"
else:
    message = "On ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives entre l'âge et le panier moyen"   
Text_message(message)

# Modèle des moindres carrés ordinaires (MCO) (OLS en anglais) 
message = "Test Anova suivant le modèle des moindres carrés ordinaires - 3 tranches d'âges"
Text_message(message)
# Adaptation des colonnes pour l'Anova
TAge_panier_melt.columns = ['Age', "tranchages", "valeur"]
model = ols('valeur ~ C(tranchages)', data=TAge_panier_melt).fit()
anova_table = sm.stats.anova_lm(model)
# Renommer les colonnes
TAge_panier_melt.columns = ['Age', "Age-Range", "Panier moyen"]
display(anova_table)
p_value = anova_table.loc['C(tranchages)', 'PR(>F)']
alpha = 0.05  # Niveau de signification
if p_value < alpha:
    message = "On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen"
else:
    message = "On ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y a des différences significatives entre l'âge et le panier moyen"   
Text_message(message)

# Graphique
TAge_panier18 = TAge_panier_melt.loc[TAge_panier_melt['Age'] <= 31]
TAge_panier31 = TAge_panier_melt.loc[TAge_panier_melt['Age'] > 31]
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 3))
ax1twin = ax1.twiny()

sns.boxplot(x="Age-Range", y="Panier moyen", data=TAge_panier18, palette='crest',
            showfliers=True, ax=ax1)
sns.scatterplot(x="Age", y="Panier moyen", data=TAge_panier18, color='red', alpha=0.5, ax=ax1twin)
plt.grid(color='darkgrey')
ax2twin = ax2.twiny()
sns.boxplot(x="Age-Range", y="Panier moyen", data=TAge_panier31, palette='crest',
            showfliers=True, ax=ax2)
sns.scatterplot(x="Age", y="Panier moyen", data=TAge_panier31, color='red', alpha=0.5, ax=ax2twin)
plt.grid(color='darkgrey')
plt.show()
Hypothèses du test ANOVA
H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence
statistiquement significative entre les groupes.
H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence
statistiquement significative entre au moins deux groupes.
#######################################################
Test Anova suivant le modèle des moindres carrés ordinaires - 8 tranches d'âges
Age Tranche d'ages Panier moyen
0 19 18-25 40.20
1 20 18-25 40.69
2 21 18-25 40.15
df sum_sq mean_sq F PR(>F)
C(tranchages) 7.0 5261.486121 751.640874 27.849222 1.194836e-17
Residual 68.0 1835.296495 26.989654 NaN NaN
On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen
Test Anova avec f_oneway sur les 8 tranches d'âges
Age 18-25 25-35 35-45 45-55 55-65 65-75 75-85 +85
0 19 40.20 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 20 40.69 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 21 40.15 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Statistique de test = 0.98957
p-valeur = 0.43768
On ne peut pas rejeter l'hypothèse nulle, il n'y a pas suffisamment de preuves pour affirmer qu'il y
a des différences significatives entre l'âge et le panier moyen
No description has been provided for this image
No description has been provided for this image
Test Anova avec f_oneway sur les 3 tranches d'âges
Age Age-Range Panier moyen
225 92 +51 16.56
226 93 +51 17.76
227 94 +51 16.03
Statistique de test = 5.99635
p-valeur = 0.0029030241454283037
On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen
Test Anova suivant le modèle des moindres carrés ordinaires - 3 tranches d'âges
df sum_sq mean_sq F PR(>F)
C(tranchages) 2.0 7012.921423 3506.460711 3052.325198 4.428258e-71
Residual 73.0 83.861193 1.148783 NaN NaN
On rejete l'hypothèse nulle, il y a des différences significatives entre l'âge et le panier moyen
No description has been provided for this image

Test ANOVA: On rejete l'hypothèse nulle, il existe une différence significative entre les moyennes des groupes

8.5 - Corrélation entre l'âge des clients et la catégorie des livres achetés

In [65]:
# Création des Df par catégorie utile à l'analyse 
transCat0 = transanalyse.loc[transanalyse['categ'] == "0.0"]
transCat1 = transanalyse.loc[transanalyse['categ'] == "1.0"]
transCat2 = transanalyse.loc[transanalyse['categ'] == "2.0"]
transCat = transanalyse[['Age','categ']]

################################### Test de Spearman #########################
# Hypothèses du test
message = "Hypothèses du test de Spearman"
Text_message(message)
message = "H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et 'catégorie'."
Text_message(message)
message = "H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et 'catégorie'."
Text_message(message)
message = "#######################################################"
Text_message(message)
message = 'Test de Spearman'
Text_message(message)
display(transCat.tail(3))
# Données d'âge des clients (variable x) et la catégorie des livre acheté (variable y)
x = transCat['Age']
y = transCat['categ']
# Calcul de la corrélation et du p-value
correlation, p_value = st.spearmanr(x, y)
correl = correlation **2
# Affichage des résultats
message = "Corrélation de Spearman : {:.5f}".format(correlation)
Text_message(message)
message = "Corrélation de Spearman au carré (r2) : {:.4f}".format(correl)
Text_message(message)
message = "p-value : {}".format(p_value)
Text_message(message)

if p_value < alpha:
    message = "Il y a une corrélation entre l'âge et la catégorie des livres achetés, on rejette H0."
else:
    message = "Il n'y a pas de corrélation entre l'âge et la catégorie des livres achetés"
Text_message(message)

# graphique
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Intérêt des catégories suivant l'âge", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.boxplot(x="categ", y="Age", data=transCat, palette='crest', showfliers=False, showmeans=True,
           medianprops=dict(linestyle='-', linewidth=2, color='coral'),
            meanprops=dict(marker='p',markerfacecolor='white', markeredgecolor='black', markersize=8),
            boxprops = dict(linestyle='-', linewidth=0))
plt.show()
Hypothèses du test de Spearman
H0 (hypothèse nulle) : Il n'y a pas de corrélation monotone entre les variables 'Age' et
'catégorie'.
H1 (hypothèse alternative) : Il y a une corrélation monotone entre les variables 'Age' et
'catégorie'.
#######################################################
Test de Spearman
Age categ
92612 94 1.0
259123 94 1.0
413145 94 1.0
Corrélation de Spearman : -0.03658
Corrélation de Spearman au carré (r2) : 0.0013
p-value : 1.4652687232201696e-199
Il y a une corrélation entre l'âge et la catégorie des livres achetés, on rejette H0.
No description has been provided for this image

Test de Spearman :

Le r2 indique une relation monotone quasi inexistante de 1.3% entre les variables

La corrélation de Spearman de -0,04 indique une faible association négative monotone entre les variables

La p-value de 1.46e-199 indique que la probabilité d'observer une corrélation de Spearman aussi extrême que -0,04, simplement par pur hasard, est quasi nulle.

In [66]:
############################ Prépartion des listes pour le test ######################
transCat00 = transanalyse['Age'].loc[transanalyse['categ'] == "0.0"].to_list()
transCat10 = transanalyse['Age'].loc[transanalyse['categ'] == "1.0"].to_list()
transCat20 = transanalyse['Age'].loc[transanalyse['categ'] == "2.0"].to_list()

# Hypothèses du test
message = "Hypothèses du de Kruskal-Wallis"
Text_message(message)
message = "H0 (hypothèse nulle) : Les médianes des groupes sont égales, il n'y a pas de différence statistiquement significative entre les groupes."
Text_message(message)
message = "H1 (hypothèse alternative) : Au moins une médiane de groupe est différente, il y a une différence statistiquement significative entre au moins deux groupes."
Text_message(message)

################################### test de Kruskal-Wallis ############################
message = "Test de Kruskal-Wallis"
Text_message(message)
statistic, p_value = kruskal(transCat00, transCat10, transCat20)

# Afficher les résultats
alpha = 0.05  # Niveau de signification
message = "Statistique de test = {:.4f}".format(statistic)
Text_message(message)
message = "p-valeur = {}".format(p_value)
Text_message(message)
if p_value > alpha:
    message = "Les catégories ont des distributions médianes égales (hypothèse nulle acceptée)."
else:
    message ="Au moins une catégorie a une distribution médiane différente (hypothèse nulle rejetée)."
Text_message(message)
message = "#######################################################"
Text_message(message)
################################### test ANOVA ############################  
# Hypothèses du test
message = "Hypothèses du test ANOVA"
Text_message(message)
message = "H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence statistiquement significative entre les groupes."
Text_message(message)
message = "H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence statistiquement significative entre au moins deux groupes."
Text_message(message)
message = "Test Anova avec f_oneway"
Text_message(message)
fvalue, pvalue = st.f_oneway(transCat00, transCat10, transCat20)

# Afficher les résultats
alpha = 0.05  # Niveau de signification
message = "Statistique de test = {:.4f}".format(fvalue)
Text_message(message)
message = "p-valeur = {}".format(pvalue)
Text_message(message)
if pvalue > alpha:
    message = "Les catégories ont des moyennes égales (hypothèse nulle acceptée)."
else:
    message = "Il existe une différence significative entre les moyennes des catégorise  (hypothèse nulle rejetée)."
Text_message(message)    
# Graphique 
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'lightgrey'})
fig = plt.figure(figsize=(12, 3))
plt.title("Intérêt des catégories suivant l'âge", y=1.05, 
          fontdict={'size': 15, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
sns.boxplot(x="categ", y="Age", data=transanalyse, palette='crest', showfliers=False,showmeans=True,
            medianprops=dict(linestyle='-', linewidth=2, color='coral'), notch = True,
            meanprops=dict(marker='p',markerfacecolor='white', markeredgecolor='black', markersize=8),
            boxprops = dict(linestyle='-', linewidth=0))
plt.show()
Hypothèses du de Kruskal-Wallis
H0 (hypothèse nulle) : Les médianes des groupes sont égales, il n'y a pas de différence
statistiquement significative entre les groupes.
H1 (hypothèse alternative) : Au moins une médiane de groupe est différente, il y a une différence
statistiquement significative entre au moins deux groupes.
Test de Kruskal-Wallis
Statistique de test = 79095.0292
p-valeur = 0.0
Au moins une catégorie a une distribution médiane différente (hypothèse nulle rejetée).
#######################################################
Hypothèses du test ANOVA
H0 (hypothèse nulle) : Les moyennes des groupes sont égales, il n'y a pas de différence
statistiquement significative entre les groupes.
H1 (hypothèse alternative) : Au moins une moyenne de groupe est différente, il y a une différence
statistiquement significative entre au moins deux groupes.
Test Anova avec f_oneway
Statistique de test = 45722.8368
p-valeur = 0.0
Il existe une différence significative entre les moyennes des catégorise (hypothèse nulle rejetée).
No description has been provided for this image

Pour aller plus loin, vérifions la corrélation des 3 catégories sur les tranches:

18-31 ans / 31-51 ans / +51 ans

In [67]:
###################### Préparation des tables en vue de l'analyse ####################
transCat18 = Tranches.loc[(Tranches['Age'] > 18) & (Tranches['Age'] <= 31)]
transCat31 = Tranches.loc[(Tranches['Age'] > 31) & (Tranches['Age'] <= 51)]
transCat51 = Tranches.loc[Tranches['Age'] > 51]

transCat18_00 = transCat18['Age'].loc[transCat18['categ'] == "0.0"].to_list()
transCat18_10 = transCat18['Age'].loc[transCat18['categ'] == "1.0"].to_list()
transCat18_20 = transCat18['Age'].loc[transCat18['categ'] == "2.0"].to_list()

transCat31_00 = transCat31['Age'].loc[transCat31['categ'] == "0.0"].to_list()
transCat31_10 = transCat31['Age'].loc[transCat31['categ'] == "1.0"].to_list()
transCat31_20 = transCat31['Age'].loc[transCat31['categ'] == "2.0"].to_list()

transCat51_00 = transCat51['Age'].loc[transCat51['categ'] == "0.0"].to_list()
transCat51_10 = transCat51['Age'].loc[transCat51['categ'] == "1.0"].to_list()
transCat51_20 = transCat51['Age'].loc[transCat51['categ'] == "2.0"].to_list()
# Liste des noms de catégories
categories = ["18-31 ans", "31-51 ans", "+51 ans"]
# Liste des groupes de données correspondants
data_groups = [
    (transCat18_00, transCat18_10, transCat18_20),
    (transCat31_00, transCat31_10, transCat31_20),
    (transCat51_00, transCat51_10, transCat51_20)]
# Liste des dataframes correspondants
dataframes = [transCat18, transCat31, transCat51]

# Boucle for pour calculer les valeurs fvalue et pvalue pour chaque groupe de données et dataframe
for i in range(len(categories)):
    category = categories[i]
    group_data = data_groups[i]
    dataframe = dataframes[i]
    ################################ test de Kruskal-Wallis ############################  
    statistic, p_value = kruskal(*group_data)
    # Afficher les résultats
    alpha = 0.05  # Niveau de signification
    ligne0 = "Test de Kruskal-Wallis - Tranche {}".format(category)
    ligne1 = "Statistique de test = {}".format(statistic)
    ligne2 = "p-valeur = {}".format(p_value) 
    if p_value > alpha:
        ligne3 = "Les catégories ont des distributions médianes égales\n(hypothèse nulle acceptée)."
    else:
        ligne3 = "Au moins une catégorie a une distribution médiane différente\n(hypothèse nulle rejetée)."
    ################################ test ANOVA ############################    
    fvalue, pvalue = st.f_oneway(*group_data)
    # Afficher les résultats
    alpha = 0.05  # Niveau de signification
    ligne4 = "Test ANOVA - Tranche {}".format(category)
    ligne5 = "Statistique de test = {}".format(fvalue)
    ligne6 = "p-valeur = {}".format(pvalue) 
    if pvalue > alpha:
        ligne7 = "Les catégories ont des moyennes égales\n(hypothèse nulle acceptée)."
    else:
        ligne7 = "Il existe une différence significative entre les moyennes des catégories\n(hypothèse nulle rejetée)."  

    # graphique
    sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'white'})
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))

    ax1.set_title("Intérêt des catégories pour les {}".format(category), y=1.05, 
          fontdict={'size': 13, 'weight': 'bold', 'style':'italic', 'color': 'teal'})
    sns.boxplot(x="categ", y="Age", data=dataframe, palette='crest', showfliers=False,showmeans=True,
           medianprops=dict(linestyle='-', linewidth=2, color='coral'), notch = True,
            meanprops=dict(marker='p',markerfacecolor='white', markeredgecolor='black', markersize=8),
            boxprops = dict(linestyle='-', linewidth=0), ax=ax1)
    ax1.set_xlabel("")
    # Position verticale initiale du texte
    y_pos = 1
    # Ajouter chaque ligne de texte avec des variables
    ax2.text(0, y_pos, ligne0, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.1
    ax2.text(0, y_pos, ligne1, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.08
    ax2.text(0, y_pos, ligne2, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.08
    ax2.text(0, y_pos, ligne3, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.25
    ax2.text(0, y_pos, ligne4, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.1
    ax2.text(0, y_pos, ligne5, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.08
    ax2.text(0, y_pos, ligne6, horizontalalignment='left', verticalalignment='top', fontsize=12)
    y_pos -= 0.08
    ax2.text(0, y_pos, ligne7, horizontalalignment='left', verticalalignment='top', fontsize=12)
    ax2.axis('off')

plt.show()   
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Ce notebook a été réalisé à partir des fichiers d'origines non nettoyés

In [ ]: